v0.5.0a1 shipped the one actor value the spec owner had already excluded.
Caught before any pilot was notified, so it costs a tag, not a migration.
Commons decided <fast id> = process:okf-ingest on 2026-07-31, on this repo's
own proposal, superseding option (d) (process:llm-ingestion-okf) chosen here on
07-27. The exclusion is ingest-spec.md:7-8, frozen on the spec being
framework-neutral: normalising OUR repo name into the normative id would force
every other conformant implementation to write it into its own output. Verified
against three independent sources before touching anything — commons' coord
message 20260731T154140Z, their plan :215-216/:244, and their STATE :37.
Why this had to land before the pilot notifications rather than after:
actor is both the stamp written and the value owned back (OwnershipPolicy),
and recognition is one-way. A pilot that had run Test A against the excluded
id would hold bundles this library stops recognising the moment the id is
corrected — collision_unstamped on their OWN files. That is the A-E5 failure
mode, and we would have inflicted it.
Worse, it would not have shown up as a failure: the plan's A-E3 expectation
(:854) named the same excluded value as the code, so Test A would have PASSED
and confirmed the error. Expectation and implementation agreeing is not
evidence when both predate the decision.
Nothing in the wild carried the old value: OKF_V0_2 did not exist at v0.4.0, so
the profile has never been released. v0.5.0a1 is abandoned, not moved — a tag
already on a public remote does not get force-pushed, and the history should
say plainly that a1 was wrong.
A-E3 now records both corrections with dates. The V1 paragraph at :1169 is
superseded in place rather than rewritten: its reasoning still holds, only its
outcome moved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVTup4v7tW9QiVyBENk2LV
The tag was specified to land on 128109b, where the package still declared
0.4.0 in both places a version is written. The pilots install FROM the tag, so
they would have pinned a pre-release that reports the previous release.
Two requirements the "minimally requires" list did not cover, both stated
elsewhere in the plan:
- The CHANGELOG entry is load-bearing, not ceremony: the plan makes the
provisional status real by having the profile docstring AND the entry both
say the surface may change without a deprecation cycle, and name the pilot
repos. The docstring had it (profiles.py:694); the CHANGELOG stopped at
0.4.0.
- pyproject.toml and __init__.py both said 0.4.0.
Nothing in the run path reads __version__, which is why a stale one survives a
green suite. test_the_declared_version_agrees_with_the_packaged_one is the only
machine check on it — written failing first, against a one-sided bump. The tag
NAME remains a human step; no test can catch that one.
README keeps pointing at v0.4.0 on purpose: a pre-release for three named
pilots is not the general install line, and OKF_LATEST does not move until GA.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVTup4v7tW9QiVyBENk2LV
The v0.4.0 release commit claimed the [tool.uv.sources] entry "never
reaches consumers". Measured against the pushed tag in clean
environments, that is wrong in one direction and understated in the
other:
- uv, installing this package from git, DOES pick the guard up from the
tag automatically. uv reads pyproject.toml when it builds from the
source tree, so the source applies to the consumer too, not just to
development here.
- pip does not read it, and fails outright: "No matching distribution
found for llm-ingestion-guard<0.3,>=0.2". The guard must be installed
from its own tag FIRST. Verified that the guard-first order then
installs cleanly and that the shipped adapter runs against the real
guard in that environment.
What did hold is the part that matters for the pin: the built wheel
carries `Requires-Dist: llm-ingestion-guard<0.3,>=0.2`, so the range is
still the declared dependency and the git URL is still a channel.
README now states the pip order as the default instruction, with the uv
shortcut beside it, because a consumer hitting the resolution error would
otherwise read it as a broken release.
Phase 2 ships: Doors B and C, the extraction registry, the guard adapter,
and with them the first and only runtime dependency this library takes.
The minor bump is the news consumers are waiting on -- installing the
package now installs llm-ingestion-guard>=0.2,<0.3, though importing it
still does not import the guard.
Two behaviour changes carried in the same release are called out in the
changelog rather than buried: an extraction title containing `[` or `]`
now fails at manifest load (ingest-spec section 4), and the section 3
ownership scan no longer deletes files another manifest stamped into the
same bundle.
uv.lock was stale -- it predated the guard dependency and could not be
regenerated, because the guard is not on a package index. A
[tool.uv.sources] entry pins the same tag the README documents, so
`uv lock`/`uv sync` resolve. It is uv-specific dev metadata: the built
wheel still carries `Requires-Dist: llm-ingestion-guard<0.3,>=0.2`,
verified against the wheel, so the range remains the pin and the git URL
remains a channel.
Binary extraction (pdf/docx/xlsx behind [extract]) is deliberately not in
this release and is stated as outstanding in both README and changelog.
425 tests green; ruff, ruff format and mypy --strict clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
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).
Backfills CHANGELOG.md, which was empty despite two prior tags: entries for
0.1.0 (untagged, pinned by commit), 0.2.0, 0.3.0, and this release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdVgowYC4LARgvNdNMiuvz
TDD step 1 of the phase 1 plan: typed error hierarchy rooted in
IngestError, manifest parsing with schema validation before any source
call — id grammar, polymorphic source types, extraction rules, verdict
reservation (okf_type and filename namespace), and the no-embedded-
credentials rule for base_url. Dev tooling (pytest/mypy/ruff) added as
a PEP 735 dependency group; runtime dependencies stay empty.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QeqhJpYQyghASjiJo5EhGg