release(0.4.0): the raise is breaking, so the number says so
okf answered the exposure question with a measured no — zero call sites for the three capped surfaces anywhere in their src/, and their screen_output path reaches only scan_output, which truncates. So the blast radius is zero and 0.3.5 would have hurt nobody. It is still 0.4.0. Adding a raise to a previously total public function is breaking under SemVer, and this file declares SemVer; a survey that comes back clean tells you the upgrade is easy, not that the contract held. The version number describes the change, not the luck of who happens to call it. Cost, stated: 0.4.0 was penciled in for the axis separation. That moves to 0.5.0. Tags are never moved, so the choice is made once. README still advertises v0.3.4 — it is updated after a clean-venv install proves the tag resolves, so the install block never points at something unproven.
This commit is contained in:
parent
b90233481a
commit
73ad5e1ed8
3 changed files with 42 additions and 20 deletions
58
CHANGELOG.md
58
CHANGELOG.md
|
|
@ -7,19 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
> **Behaviour change, not a pure fix.** The three transform surfaces gain a
|
||||
> refusal path they did not have. A caller that today passes a document larger
|
||||
> than 1 000 000 characters gets an exception where it previously got a result.
|
||||
Nothing yet.
|
||||
|
||||
|
||||
## [0.4.0] — 2026-08-10
|
||||
|
||||
> **Behaviour change, not a pure fix — and that is why this is 0.4.0 and not
|
||||
> 0.3.5.** The three transform surfaces gain a refusal path they did not have. A
|
||||
> caller that passes a document larger than 1 000 000 characters now gets an
|
||||
> exception where it previously got a result. Adding a raise to a function that
|
||||
> was previously total is breaking under SemVer whatever the measured blast
|
||||
> radius turns out to be, so the number follows the change, not the survey.
|
||||
>
|
||||
> **The two consumers are not pinned the same way, and it matters here.**
|
||||
> `linkedin-studio` pins an exact tag, so nothing reaches it until it re-pins.
|
||||
> `llm-ingestion-okf` moved to the range `>=0.3,<0.4` (their `f536e13`), so any
|
||||
> release on the 0.3 line lands on them at their next resolve, without an action
|
||||
> on their part. Whether that can affect them at all depends on which surfaces
|
||||
> their pipeline calls — `okf.py` itself calls none of the three — and that is
|
||||
> being measured with them before this ships. Which is also why the version this
|
||||
> lands under is not yet decided: on the 0.3 line it arrives unasked; as 0.4.0 it
|
||||
> sits outside their ceiling and they opt in.
|
||||
> **The measured blast radius, for the record: zero.** `linkedin-studio` pins an
|
||||
> exact tag, so nothing reaches it until it re-pins. `llm-ingestion-okf` moved to
|
||||
> the range `>=0.3,<0.4` (their `f536e13`), so a 0.3.5 would have landed on them
|
||||
> at their next resolve without an action on their part — but they answered our
|
||||
> query (`20260802T193351Z`) with a measured **no**: zero call sites for
|
||||
> `sanitize` / `fence` / `neutralize` / `prepare_input` anywhere in their `src/`.
|
||||
> Their `screen_output` path reaches only `scan_output`, which truncates and does
|
||||
> not raise. Releasing as 0.4.0 puts this outside their ceiling regardless, so
|
||||
> they cross it deliberately rather than by resolving.
|
||||
|
||||
### Added — input-size cap on the transform surfaces (OWASP LLM10)
|
||||
|
||||
|
|
@ -47,13 +55,27 @@ the cap, `details` names the refusing surface, and neither carries input.
|
|||
`max_input_chars` is a per-call parameter, defaulting to the single calibrated
|
||||
constant.
|
||||
|
||||
### Still uncapped, and deliberately
|
||||
### Added — the last two detection surfaces bound their input too
|
||||
|
||||
`scan_active_content` **called directly** and the okf link graph. Reached through
|
||||
`scan_output`, `scan_active_content` inherits that function's cap. Both are
|
||||
detection-shaped, so the truncate-and-flag mechanism transfers to them unchanged
|
||||
— mechanical follow-up work, not a policy question. Recorded in
|
||||
`docs/LIMITATIONS.md`.
|
||||
`scan_active_content` **called directly** and `okf.link_graph` were the two
|
||||
surfaces still reading attacker-supplied text with no cap. Both truncate and
|
||||
record, the way the other scanners do:
|
||||
|
||||
- `scan_active_content(text, source, max_scan_chars=MAX_SCAN_CHARS)` emits one
|
||||
`active:oversize-input` finding (MEDIUM, LLM10) and scans the prefix. Reached
|
||||
through `scan_output` the text is already under that surface's cap, so the flag
|
||||
is raised once, there — `max_scan_chars` is now passed down.
|
||||
- `link_graph(bundle, max_scan_chars=MAX_SCAN_CHARS)` caps each body and records
|
||||
`(from_id, body_length)` in the new `LinkGraphResult.truncated` field. The
|
||||
field is additive with a default, so existing positional construction and
|
||||
attribute access are unaffected.
|
||||
|
||||
**What truncation costs is named rather than implied:** past the cap, "no
|
||||
finding" means "not looked at". That is precisely what a silent truncation would
|
||||
hide, and why `truncated` exists as a field instead of a log line — it is what
|
||||
separates "no links past here" from "no links *read* past here".
|
||||
|
||||
Recorded in `docs/LIMITATIONS.md`.
|
||||
|
||||
|
||||
## [0.3.4] — 2026-08-01
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "llm-ingestion-guard"
|
||||
version = "0.3.4"
|
||||
version = "0.4.0"
|
||||
description = "Write-time defensive layer for Python pipelines that persist LLM output: sanitize, fence, tool-less quarantined transform, capability isolation, scan before persist, fail-secure."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ from .grounding import (
|
|||
)
|
||||
from . import okf
|
||||
|
||||
__version__ = "0.3.4"
|
||||
__version__ = "0.4.0"
|
||||
|
||||
|
||||
# --- §6 bookends: the two library-side halves around the transform ---------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue