llm-ingestion-okf/tests/test_packaging.py
Kjell Tore Guttormsen 241e00f27a feat(guard): wire Doors B and C to the real guard (Phase 2 step 4)
Adds llm_ingestion_guard>=0.2,<0.3 as this library's first and only runtime
dependency, and guard_adapter.py -- the one module that imports it. The flows
themselves are unchanged: they still take an injected gate, and importing the
package still does not import the guard, so a Door A consumer is unaffected by
the dependency's state.

Door B screens the exact bytes it persists. The guard's §6 bookends
(prepare_input -> model -> screen_output) assume a model call in between; this
library makes none, and prepare_input returns prompt-shaped text (sanitized
AND spotlight-fenced with a per-call nonce) that must never reach disk. So the
adapter calls screen_output alone, on the extracted text as it stands, and
hands that same text back -- the verdict is then a statement about the bytes
actually written. This supersedes the plan's "bookends" wording, recorded
there under "Settled during implementation (step 4)".

It follows that the gate refuses rather than repairs: a file carrying an
invisible carrier is rejected, not stripped and persisted. Sanitizing first
would write a document differing invisibly from the operator's file while
source_sha256 still points at the original bytes. Operator decision; the
policy is PRESET_USER_UPLOAD, so any finding at all is held back.

Door C hands the bundle over whole to okf.import_bundle, which resolves the
cross-link graph across concepts. Per-concept reasons are derived from the
scan findings (severity:label) because stamp_concept keeps the disposition and
drops the reason strings behind it.

Assumption B1 closes as a signature smoke test over what the adapters actually
call -- screen_output and okf.import_bundle signatures, the Disposition values
both doors compare by value, the Origin/Channel vocabularies Door C validates,
the result fields read, and the upload preset's shape. prepare_input is not
pinned: drift there cannot reach this library. Behaviour is pinned against the
real scanner too, including the persist-gate proof that a fail-secure fixture
leaves the bundle byte-identical.

B2 closes with it: git+https tag install over anonymously readable HTTPS, no
credential. A direct reference is an install-time channel, not the pin -- the
range stays in pyproject, is satisfied by the tag install today, and resolves
normally once the package index exists. A packaging test enforces that the
guard remains the only runtime dependency (verified by hand-mutation).
2026-07-25 07:32:45 +02:00

34 lines
1.3 KiB
Python

"""Packaging contract: a PEP 561 typed package with exactly one dependency.
Consumers run mypy --strict against the inline annotations; without the
py.typed marker mypy degrades every imported symbol to Any.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import llm_ingestion_okf
PROJECT_ROOT = Path(__file__).resolve().parents[1]
def test_package_ships_py_typed_marker() -> None:
package_dir = Path(llm_ingestion_okf.__file__).parent
assert (package_dir / "py.typed").is_file()
def test_the_only_runtime_dependency_is_the_security_boundary() -> None:
"""The stdlib-only rule, enforced rather than asserted in prose.
One dependency is permitted — the guard — because security is the one
thing this library must not implement. Everything else stays stdlib, so
a consumer vendoring this package takes on no transitive surface. The
version RANGE is the pin: it resolves against a package index, and is
satisfied by the git+https tag install until that index exists.
"""
tomllib = pytest.importorskip("tomllib") # stdlib from 3.11; the pin holds on 3.10 too
pyproject = tomllib.loads((PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
assert pyproject["project"]["dependencies"] == ["llm-ingestion-guard>=0.2,<0.3"]