diff --git a/src/llm_ingestion_guard/__init__.py b/src/llm_ingestion_guard/__init__.py index 99f5782..252ff7f 100644 --- a/src/llm_ingestion_guard/__init__.py +++ b/src/llm_ingestion_guard/__init__.py @@ -56,6 +56,7 @@ from .grounding import ( no_grounding_check, DEFAULT_GROUNDING_CHECK, ) +from . import okf __version__ = "0.1.0" @@ -142,4 +143,6 @@ __all__ = [ "SourceGroundingCheck", "no_grounding_check", "DEFAULT_GROUNDING_CHECK", # §6 bookends "prepare_input", "screen_output", "PreparedInput", + # OKF adapter (v0.2) — the format-specific layer, as its own namespace + "okf", ] diff --git a/src/llm_ingestion_guard/okf.py b/src/llm_ingestion_guard/okf.py index a2ee715..d3c29c0 100644 --- a/src/llm_ingestion_guard/okf.py +++ b/src/llm_ingestion_guard/okf.py @@ -315,10 +315,17 @@ class ConceptResult: @dataclass(frozen=True) class BundleResult: - """Per-concept results plus the bundle's aggregate (most-severe) disposition.""" + """Per-concept results, the aggregate disposition, and the cross-link graph. + + ``links`` is the in-import :class:`LinkGraphResult` for the whole bundle + (dangling / rejected / resolved edges), so a mode-b import returns both halves + of the gate together. Whether a dangling or rejected link should block is the + caller's disposition call (design principle 4). + """ concepts: tuple disposition: Disposition + links: "LinkGraphResult" def log(self): """The ``log.md`` body — one line per concept, rejected ones marked.""" @@ -347,7 +354,7 @@ def import_bundle(bundle, *, origin=Origin.EXTERNAL, channel=Channel.AUTOMATIC): for path in sorted(bundle) ) aggregate = _most_severe(r.disposition for r in results) - return BundleResult(results, aggregate) + return BundleResult(results, aggregate, link_graph(bundle)) def _validate_concept(path, doc, origin, channel): diff --git a/tests/test_okf.py b/tests/test_okf.py index 8f3553f..d02f272 100644 --- a/tests/test_okf.py +++ b/tests/test_okf.py @@ -444,3 +444,22 @@ def test_link_graph_records_rejected_dangerous_link(): bundle = {"a/main.md": "---\ntype: t\n---\n[x](javascript:alert(1))\n"} graph = link_graph(bundle) assert any(from_id == "a/main" for from_id, _target, _reason in graph.rejected) + + +# --- wiring: import_bundle carries the cross-link graph, and the package +# exposes the okf adapter as a first-class namespace ---------------------- + +def test_import_bundle_attaches_link_graph(): + bundle = { + "a/main.md": "---\ntype: t\n---\nSee [later](/b/target.md).\n", + "a/other.md": "---\ntype: t\n---\nNothing linked here.\n", + } + result = import_bundle(bundle) + assert ("a/main", "b/target") in result.links.dangling + + +def test_okf_adapter_is_exposed_from_package(): + import llm_ingestion_guard as guard + + assert "okf" in guard.__all__ + assert guard.okf.import_bundle is import_bundle