"""Door B inbox provenance rendering + filename slugging (Phase 2 step 2). Pure functions, zero runtime dependency, no guard call: the slug reduces a dropped file's name to the Phase 1 id grammar, the `inbox-` namespace keeps generated concepts disjoint from `index.md`/`ingest-*`/`promoted-verdict-*`, and the provenance layer carries the ยง7-analogous honesty marker. The verdict reservation applies here unchanged โ€” the promotion gate stays the only path into that layer. """ from __future__ import annotations import hashlib import re import pytest from llm_ingestion_okf import MaterializationError from llm_ingestion_okf.inbox import inbox_filename, inbox_slug, render_inbox_concept INGESTED_AT = "2026-07-25T12:00:00Z" # Phase 1's id grammar (manifest.py `_ID_PATTERN`), restated independently so a # change to that constant cannot silently widen what the inbox namespace admits. ID_GRAMMAR = re.compile(r"[a-z0-9][a-z0-9-]*\Z") # Names chosen to attack the grammar from every side: spaces, punctuation runs, # case, unicode, path-ish characters, leading/trailing separators, digits first. NASTY_NAMES = [ "My Notes 2026.md", " padded .txt", "UPPER.CSV", "a__b c.txt", "--weird--.md", "2026-report.md", "caf\u00e9.md", "re:port(final)[v2].md", "dots.in.name.md", "tab\tseparated.txt", ] # --- slug: reduction to the Phase 1 id grammar --- def test_slug_reduces_filename_to_the_phase_1_id_grammar() -> None: assert inbox_slug("My Notes 2026.md") == "my-notes-2026" def test_slug_drops_the_extension() -> None: assert inbox_slug("report.csv") == "report" def test_slug_keeps_inner_dots_as_separators() -> None: # A dot is not in the grammar; only the final extension is special. assert inbox_slug("dots.in.name.md") == "dots-in-name" def test_slug_collapses_separator_runs_to_a_single_hyphen() -> None: assert inbox_slug("a__b c.txt") == "a-b-c" def test_slug_strips_leading_and_trailing_separators() -> None: assert inbox_slug("--weird--.md") == "weird" def test_slug_lowercases() -> None: assert inbox_slug("UPPER.CSV") == "upper" def test_slug_allows_a_leading_digit() -> None: # The Phase 1 grammar admits `[a-z0-9]` as the first character. assert inbox_slug("2026-report.md") == "2026-report" def test_slug_replaces_non_ascii_rather_than_transliterating() -> None: # No guessing at a romanisation: every character outside the grammar is a # separator. Transliterating would be a semantic claim the slugger cannot # make - Norwegian "m\u00f8te" (meeting) would become "mote" (fashion). # The readable name survives verbatim in `title:`; the slug is an id. assert inbox_slug("caf\u00e9.md") == "caf" def test_slug_is_stable_across_unicode_normalisation_forms() -> None: """macOS (APFS) hands filenames over DECOMPOSED. Without NFC normalisation the combining mark alone becomes a separator and the base letter survives, so one visual filename would slug two different ways depending on whether it arrived from a directory listing or from an operator-typed string. Escapes, not literals: the test must not depend on this file's own form. """ composed = "caf\u00e9.md" # NFC: e-acute as one code point decomposed = "cafe\u0301.md" # NFD: plain e + combining acute assert composed != decomposed assert inbox_slug(composed) == inbox_slug(decomposed) == "caf" def test_slug_does_not_transliterate_nordic_letters() -> None: assert inbox_slug("m\u00f8te-notat.md") == "m-te-notat" @pytest.mark.parametrize("name", NASTY_NAMES) def test_slug_output_always_matches_the_id_grammar(name: str) -> None: assert ID_GRAMMAR.match(inbox_slug(name)) def test_slug_is_deterministic() -> None: assert inbox_slug("My Notes 2026.md") == inbox_slug("My Notes 2026.md") def test_slug_of_a_hidden_file_uses_its_leading_dot_name() -> None: # A dotfile has no extension to drop: `.hidden.md` stems to `.hidden`. assert inbox_slug(".hidden.md") == "hidden" @pytest.mark.parametrize("name", ["---.md", "!!!.txt", "\u65e5\u672c.md", ""]) def test_slug_rejects_a_name_that_reduces_to_nothing(name: str) -> None: # Never invent a fallback name: an unusable filename is a typed per-file # failure the caller reports, not a silent `untitled`. with pytest.raises(MaterializationError) as excinfo: inbox_slug(name) assert excinfo.value.code == "inbox_slug_empty" # --- filename: the `inbox-` namespace --- def test_inbox_filename_namespaces_the_slug() -> None: assert inbox_filename("my-notes") == "inbox-my-notes.md" @pytest.mark.parametrize("name", NASTY_NAMES) def test_inbox_namespace_is_disjoint_from_the_reserved_names(name: str) -> None: """Namespace safety: no admissible slug can collide with `index.md`, a Door A concept (`ingest-*`), or a promoted verdict (`promoted-verdict-*`). """ generated = inbox_filename(inbox_slug(name)) assert generated != "index.md" assert not generated.startswith("ingest-") assert not generated.startswith("promoted-verdict-") assert generated.startswith("inbox-") # --- provenance rendering --- def test_frontmatter_keys_in_exact_order() -> None: out = render_inbox_concept( "Body text", okf_type="note", title="My Notes", source_file="My Notes 2026.md", source_bytes=b"original", ingested_at=INGESTED_AT, ) digest = hashlib.sha256(b"original").hexdigest() assert out == ( "---\n" "type: note\n" "title: My Notes\n" "source_file: My Notes 2026.md\n" f"source_sha256: {digest}\n" f"ingested_at: {INGESTED_AT}\n" "generated: true\n" "---\n" "\n" "Body text\n" ) def test_source_sha256_is_over_the_original_bytes_not_the_extracted_text() -> None: """The honesty marker must point at what was DROPPED, not at what the extractor produced โ€” otherwise provenance cannot be re-verified against the operator's file. """ raw = b"name\nAda\n" text = "| name |\n| --- |\n| Ada |\n" out = render_inbox_concept( text, okf_type="note", title="Data", source_file="data.csv", source_bytes=raw, ingested_at=INGESTED_AT, ) assert f"source_sha256: {hashlib.sha256(raw).hexdigest()}\n" in out assert hashlib.sha256(text.encode("utf-8")).hexdigest() not in out def test_body_is_lf_only_with_exactly_one_trailing_newline() -> None: out = render_inbox_concept( "line one\r\nline two\r\n\n\n", okf_type="note", title="T", source_file="f.md", source_bytes=b"x", ingested_at=INGESTED_AT, ) assert "\r" not in out assert out.endswith("line one\nline two\n") def test_rendering_is_deterministic() -> None: kwargs = { "okf_type": "note", "title": "T", "source_file": "f.md", "source_bytes": b"x", "ingested_at": INGESTED_AT, } assert render_inbox_concept("body", **kwargs) == render_inbox_concept("body", **kwargs) # --- fail-fast gates --- @pytest.mark.parametrize("moment", ["2026-07-25", "2026-07-25T12:00:00", "not-a-time", ""]) def test_ingested_at_must_be_iso_8601_utc_with_a_z_suffix(moment: str) -> None: with pytest.raises(MaterializationError) as excinfo: render_inbox_concept( "body", okf_type="note", title="T", source_file="f.md", source_bytes=b"x", ingested_at=moment, ) assert excinfo.value.code == "ingested_at_invalid" @pytest.mark.parametrize("okf_type", ["verdict", "Verdict", "VERDICT"]) def test_verdict_okf_type_rejected(okf_type: str) -> None: """Verdict reservation, second door: the promotion gate remains the only path into that layer โ€” the inbox can never write one. """ with pytest.raises(MaterializationError) as excinfo: render_inbox_concept( "body", okf_type=okf_type, title="T", source_file="f.md", source_bytes=b"x", ingested_at=INGESTED_AT, ) assert excinfo.value.code == "okf_type_reserved" @pytest.mark.parametrize("title", ["a[b", "a]b", "two\nlines", "carriage\rreturn"]) def test_title_that_would_break_an_index_link_or_frontmatter_rejected(title: str) -> None: # Same invariant as Door A's manifest validation: the title is rendered # verbatim into `- [title](target)` and into line-oriented frontmatter. with pytest.raises(MaterializationError) as excinfo: render_inbox_concept( "body", okf_type="note", title=title, source_file="f.md", source_bytes=b"x", ingested_at=INGESTED_AT, ) assert excinfo.value.code == "inbox_title_invalid" @pytest.mark.parametrize("source_file", ["two\nlines.md", "carriage\rreturn.md"]) def test_source_file_that_would_inject_frontmatter_lines_rejected(source_file: str) -> None: with pytest.raises(MaterializationError) as excinfo: render_inbox_concept( "body", okf_type="note", title="T", source_file=source_file, source_bytes=b"x", ingested_at=INGESTED_AT, ) assert excinfo.value.code == "inbox_source_file_invalid"