feat(guard): bump the pin to >=0.3,<0.4 and pin Door C's allow_reserved=False

Measure first, widen after. The 19-fixture guard-surface suite was re-run
against v0.3.4 in a scratch venv before the range moved, and reproduced the
three deltas measured against v0.3.3 exactly, with none added. v0.3.4 is the
tag pinned rather than v0.3.3 because it shipped first and repairs a quadratic
regex (okf._MD_LINK_RE) that sits on Door C's own call path.

Door C now passes allow_reserved=False explicitly. The guard added the keyword
in the 0.3 line and defaults it True for received bundles, which would merge a
sender's index.md / log.md instead of rejecting them. The override keeps the
unconditional reserved-name refusal committed to before the keyword existed,
and the reason is structural rather than a second opinion on the guard's scan:
Door C generates the merged bundle's index.md from what it merged and writes
every merged concept verbatim, so a sender's index.md would be a second and
irreconcilable claim on one path.

This is not a behaviour change for anyone on the previous pin: under v0.2.0
the keyword did not exist and reserved names were refused by construction.

The floor is >=0.3 and not >=0.2 for a measured reason. allow_reserved is
absent in v0.2.0 and present from v0.3.0 onward, checked across all five tags:
a >=0.2 floor would admit a version that raises TypeError on every Door C
import. That measurement also corrects a recorded premise -- the plan said the
keyword "shipped in v0.3.3", which read the first version we ran the suite
against as the version it was introduced in. The conclusion held; the reason
did not, and the reason is what a future bump would have relied on.

test_door_c_pins_allow_reserved_false_against_the_guards_default locks both
halves: that the guard still defaults True, without which the override is a
no-op that would pass forever over nothing, and that Door C overrides it.

586 tests, mypy --strict clean, goldens byte-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V2v1hrDhrff2H3y2TNJHkF
This commit is contained in:
Kjell Tore Guttormsen 2026-08-02 21:08:53 +02:00
commit f536e1384d
12 changed files with 156 additions and 34 deletions

View file

@ -22,6 +22,7 @@ from __future__ import annotations
import inspect
from pathlib import Path
from typing import Any
import pytest
@ -57,11 +58,11 @@ def _snapshot(directory: Path) -> dict[str, bytes]:
def test_guard_version_is_inside_the_pin() -> None:
"""The pin is `>=0.2,<0.3`; a 0.3 in the environment invalidates every
"""The pin is `>=0.3,<0.4`; a version outside it invalidates every
by-value comparison below and must fail loudly rather than be discovered
through a mis-branched verdict."""
major, minor = (int(part) for part in guard.__version__.split(".")[:2])
assert (major, minor) == (0, 2), guard.__version__
assert (major, minor) == (0, 3), guard.__version__
def test_guard_screen_output_signature_is_what_door_b_calls() -> None:
@ -73,11 +74,48 @@ def test_guard_screen_output_signature_is_what_door_b_calls() -> None:
def test_guard_import_bundle_signature_is_what_door_c_calls() -> None:
parameters = inspect.signature(guard_okf.import_bundle).parameters
assert list(parameters) == ["bundle", "origin", "channel"]
assert list(parameters) == ["bundle", "origin", "channel", "allow_reserved"]
# origin/channel keyword-only at the guard too: a transposed positional
# call would move a bundle between trust tiers with no type error.
assert parameters["origin"].kind is inspect.Parameter.KEYWORD_ONLY
assert parameters["channel"].kind is inspect.Parameter.KEYWORD_ONLY
assert parameters["allow_reserved"].kind is inspect.Parameter.KEYWORD_ONLY
def test_door_c_pins_allow_reserved_false_against_the_guards_default(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Door C passes `allow_reserved=False` EXPLICITLY, and that is load-bearing.
The guard defaults it `True` on the mode-b received-bundle path, reasoning
that `index.md`/`log.md` are legitimate structural files in a conformant
third-party bundle. Door C IS that path and overrides it anyway, because
this library GENERATES the bundle's `index.md` from what it merged: a
sender's `index.md`, which Door C's other invariant would write verbatim,
is a second and unreconcilable claim about the same file. The refusal is
not a security judgement layered over the guard's — it is this library's
own structural one, and it is the posture the phase-2 plan committed to
before the kwarg existed.
The first assertion is why this test cannot be dropped as redundant: the
override only means something while the guard's default disagrees with it.
Were the guard to default `False` later, the explicit kwarg would become a
no-op and this test says so, rather than passing forever over nothing.
"""
parameters = inspect.signature(guard_okf.import_bundle).parameters
assert parameters["allow_reserved"].default is True
captured: dict[str, object] = {}
real_import_bundle = guard_okf.import_bundle
def _spy(bundle: dict[str, str], **kwargs: Any) -> Any:
captured.update(kwargs)
return real_import_bundle(bundle, **kwargs)
monkeypatch.setattr(guard_adapter.guard_okf, "import_bundle", _spy)
guard_adapter.import_gate({"index.md": BENIGN}, origin="external", channel="automatic")
assert captured["allow_reserved"] is False
def test_disposition_vocabulary_matches_the_constants_the_doors_branch_on() -> None:

View file

@ -31,7 +31,7 @@ def test_the_only_runtime_dependency_is_the_security_boundary() -> None:
"""
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"]
assert pyproject["project"]["dependencies"] == ["llm-ingestion-guard>=0.3,<0.4"]
def test_the_declared_version_agrees_with_the_packaged_one() -> None: