LIMITATIONS said "Since 0.3.5" and the README stated the new refusal in present
tense under a 0.3.4 badge and a @v0.3.4 install pin. Both are on the public
mirror, and both contradict the same commit's CHANGELOG ("the version this lands
under is not yet decided") and what we told llm-ingestion-okf an hour earlier:
no tag until they answer.
A reader installing the advertised pin would get a library that does not do what
the README says. Same failure the clean-venv rule already covers for tags —
extended to behaviour.
Also fixes a test that passed for the wrong reason: `"101" not in details` is
tuple membership over ("sanitize",), trivially true, and would stay true if a
size were ever folded into the string. Now a substring check, matching the
canary assertion two lines above it.
The scanners cap by truncating: they return findings, so reading a prefix costs
detection in the tail and nothing else. The three transform surfaces return
*content*, where the same move is not available — a shortened document is silent
data loss, and a transformed prefix followed by an untransformed tail is a
bypass, since the attacker chooses where in the document the payload sits.
So they fail secure instead. Above MAX_INPUT_CHARS (1 000 000) sanitize, fence
and neutralize raise OversizeInputError. sanitize is step 1 of prepare_input and
only ever removes, so that one refusal bounds the whole input path.
OversizeInputError subclasses ContractViolation: a pipeline already bracketing
its quarantined stage keeps failing closed rather than meeting a type it has
never heard of. It inherits the alert-routable property too — sizes in the
message, refusing surface in details, no input in either.
Invariant now pinned across all three: returned text is always fully
transformed, or not returned at all.
Still uncapped and recorded in LIMITATIONS: scan_active_content called directly
(through scan_output it inherits that cap) and the okf link graph. Both are
detection-shaped, so truncate-and-flag transfers unchanged — mechanical, not
policy.
699 tests (+23), coverage 128/128 + 6/6, ReDoS sweep 0 candidates / 150.