Shared OKF (Open Knowledge Format) ingestion library: spec-based connectors, bundle inbox, and external-bundle import. Security delegated to llm-ingestion-guard.
Find a file
Kjell Tore Guttormsen 128109bef0 docs(okf-v0.2): the F1 disposition, and A-E6's rationale after catalog 1ca27f6
Three corrections to the alignment plan, two of them because a fact a peer
repo gave us stopped being true.

**P1 dispositions.** F1 is fixed before the tag; the other five are not. The
line is whether a finding bites in code the pilots will pin. F1 did. F2/F3
do not: measured, all three `TypePolicy.rejection` call sites hard-code
`DEFAULT.types`, and both `DEFAULT` and `OKF_V0_2` set `allowed=None`, so the
closed-set branch is unreachable in shipped code and fires only for a caller
constructing `STRICT_V1` directly — which is what the sweep did. Dequoting in
`TypePolicy` would repair a write path that never sees a quote, and would fix
the meaning of a quote without a value model able to express one. F2/F3/F4
are one root cause and go to D1b; F5/F6 stay P4's.

**A-E6's reason did not survive, though the expectation did.** It justified
"unquoted" as a property of catalog's gate — their shape regex read quotes as
part of the value and so rejected upstream's own §12:773 example. Catalog
resolved that in `1ca27f6`: from spec 0.3 the value is unquoted before the
form check and both forms pass. The expectation stands on a different ground
now, supplied by commons the same day: `method-spec.md:90` parses frontmatter
line-oriented, so a quote is a character in the value rather than syntax a
parser removes, and upstream emits the same way. Recorded as a supersession
rather than swapped silently — reading a stale rationale as current is this
expectation's own defect class.

**TEST B must name the reader revision.** `okf-check.mjs` changed in the same
commit, and the reader's placement-blindness is now a deliberate choice
rather than an unanchored regex. A report that does not say which revision it
ran is not reproducible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JqCmfJ2ukpFXeFjfab8wvy
2026-07-31 18:16:30 +02:00
docs docs(okf-v0.2): the F1 disposition, and A-E6's rationale after catalog 1ca27f6 2026-07-31 18:16:30 +02:00
examples feat(okf-v0.2): D5 — the v0.2 golden fixture, with okf_version in root frontmatter 2026-07-31 17:27:48 +02:00
src/llm_ingestion_okf fix(profiles): P1-F1 — a permitted root key is not a required one 2026-07-31 18:16:13 +02:00
tests fix(profiles): P1-F1 — a permitted root key is not a required one 2026-07-31 18:16:13 +02:00
.gitignore feat: initial commit — repo scaffold and v1 scope 2026-07-16 10:12:59 +02:00
CHANGELOG.md docs: two corrections from the convention owner and the spec owner 2026-07-25 12:32:21 +02:00
CLAUDE.md feat(okf-v0.2): D5 — the v0.2 golden fixture, with okf_version in root frontmatter 2026-07-31 17:27:48 +02:00
LICENSE feat: initial commit — repo scaffold and v1 scope 2026-07-16 10:12:59 +02:00
pyproject.toml docs(install): correct how the guard reaches a consumer, measured both ways 2026-07-25 12:24:54 +02:00
README.md feat(okf-v0.2): D5 — the v0.2 golden fixture, with okf_version in root frontmatter 2026-07-31 17:27:48 +02:00
uv.lock chore(release): v0.4.0 2026-07-25 12:08:48 +02:00

llm-ingestion-okf

Shared ingestion library for OKF (Open Knowledge Format) bundles.

Status: phases 1 and 2 are implemented. Phase 1 (spec-based ingestion) covers manifest validation, the file/sql/http connectors, deterministic materialization, index generation, and the golden fixture suite under examples/. Phase 2 adds the bundle inbox (process_inbox) and external-bundle import (import_bundle), both against an injected persist gate, with llm_ingestion_okf.guard_adapter wiring that gate to the real guard (see below). One phase-2 item is deliberately outstanding: binary extraction (pdf/docx/xlsx behind the [extract] extra) is unimplemented, so those types are rejected fail-fast. Phases 34 are planned (see docs/plan/).

Planned scope (v1)

The library provides three entry points for getting content into an OKF bundle:

  1. Spec-based ingestion. An implementation of the normative ingest specification owned by portfolio-optimiser-commons: manifest → file/sql/http connector → deterministic materialization of ingest-{id}.md concept files → index generation. Zero model calls in the run path; output is reproducible byte-for-byte against golden fixtures.
  2. Bundle inbox. A drop directory where common file types are converted to OKF concept files. All file-type→text extraction lives in this library: md, txt, csv, json, and html are handled by the stdlib core; pdf, docx, and xlsx require the optional [extract] extra and are rejected fail-fast without it. Extracted text passes the security gate before anything is persisted.
  3. External bundle import. Import and merge of third-party OKF bundles: each concept is assessed via the security gate, and only concepts that pass are merged, materialized, and linked into the index.

Boundary: security is delegated

Security is owned by the sibling package llm-ingestion-guard (pinned >=0.2,<0.3). The division is strict:

  • guard answers "is this content safe to persist?" — scan, sanitize, quarantine, fail-secure, provenance stamping.
  • this library does the plumbing — connect a source, materialize a deterministic OKF bundle, generate the index.

No security functionality is reimplemented here.

What is gated today: read this before trusting a door

  • Door A (materialize_bundle) is ungated. It calls nothing before writing to disk and writes what it is given. A caller materializing untrusted content is responsible for gating it.
  • Doors B and C (process_inbox, import_bundle) gate through an adapter you pass in. Each takes a gate argument; the flow hands it the content and obeys the verdict, refusing to persist anything that does not clear the guard's non-blocking floor — including a disposition it does not recognise, and (at Door C) a concept the gate returned no verdict for. What it cannot do is check that your adapter is a real guard: a permissive stub approves everything, and the flow will believe it.

llm_ingestion_okf.guard_adapter is the adapter over the real guard, and the only module here that imports it — importing the package itself does not:

from llm_ingestion_okf import process_inbox
from llm_ingestion_okf.guard_adapter import inbox_gate

result = process_inbox(inbox_dir, bundle_dir, "2026-07-25T12:00:00Z",
                       okf_type="reference", gate=inbox_gate)

Two properties of that adapter are worth knowing before you rely on it. It screens the exact bytes it persists — the guard's prepare_input bookend prepares text for a model call, which this library never makes, so only screen_output is used and the screened string is the written string. And it refuses rather than repairs: a file carrying an invisible zero-width or bidi character is rejected, not silently stripped and written. Door B screens under the untrusted-upload policy, so any finding at all is held back rather than persisted.

This section is stated plainly because earlier wording ("calls the guard at every persist gate") described the intended end state in the present tense, and a consumer reasonably read it as safe-by-default.

Roadmap

The library is built in four phases so that every known OKF surface in the ecosystem is eventually covered. Each phase has a detailed plan with verification criteria:

  1. Spec-based ingestion (Python) with byte-exact golden fixtures — plan.
  2. Bundle inbox and external-bundle import (Python), guard-gated — plan.
  3. Configurable bundle contract (types, layers, frontmatter sets, index shape, and reserved-file policy as configuration), enabling stricter bundle profiles such as strict-v1plan.
  4. A node/ half: a zero-dependency Node/ESM package (importable and CLI-invokable, vendored per consumer) providing bundle checking, index generation, inbox processing, and document conversion for the OKF second-brain plugin ecosystem. The Python and Node halves share the OKF contract and fixture suite, not code — plan.

Upstream OKF versions

The library targets the current latest version of Google's OKF. Support is additive — a new upstream version arrives as a new profile, never as a migration of an existing one — so upgrading the library does not change the bytes an existing profile emits.

Profile Contract Status
DEFAULT commons' ingest-spec §5 layer (OKF v0.1 semantics) stable
STRICT_V1 a consumer's ratified v0.1 contract stable
OKF_V0_2 OKF v0.2 provisional, pre-release only
OKF_LATEST alias for the latest version supported as stable currently DEFAULT

OKF_V0_2 ships first as a pre-release to a named pilot set and may change on their feedback without a deprecation cycle. Pin the versioned constant rather than OKF_LATEST unless you have explicitly opted into tracking; OKF_LATEST moves at general availability, which is a deliberate release event rather than a side effect of an upgrade.

Selecting a profile is keyword-only, so existing call sites are unaffected:

materialize_bundle(manifest, bundle_dir, ingested_at, profile=OKF_V0_2)

A bundle may declare the version it targets. OKF v0.2 §12 makes this a MAY, and puts the declaration in the bundle-root index.md's frontmatter block. The profile names the key; the caller supplies the value, because that value tracks the upstream version and is not this library's to decide:

materialize_bundle(
    manifest, bundle_dir, ingested_at,
    profile=OKF_V0_2,
    root_frontmatter_values={"okf_version": "0.2"},
)

Omit the argument and no frontmatter block is written. Offering a key the profile does not name is refused before anything is written to disk.

Non-goals

  • Verdict/feedback machinery from the method specification (stays in the consuming repositories).
  • Embedding- or retrieval-layer functionality.
  • Security functionality, in either runtime — that is always llm-ingestion-guard's domain.

Requirements

Python 3.10+, and exactly one runtime dependency — the security boundary, llm-ingestion-guard>=0.2,<0.3. Everything else is stdlib.

That guard is not on a package index yet, so with pip, install it first — otherwise installing this package fails with No matching distribution found for llm-ingestion-guard:

pip install "llm-ingestion-guard @ git+https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git@v0.2.0"
pip install "llm-ingestion-okf @ git+https://git.fromaitochitta.com/open/llm-ingestion-okf.git@v0.4.0"

With uv, one command is enough — uv pip install "llm-ingestion-okf @ git+…@v0.4.0" resolves the guard from the tag on its own, because uv reads the [tool.uv.sources] entry in this project's pyproject.toml when it builds from the source tree. Both paths were measured on 2026-07-25.

A git URL is a PEP 508 direct reference and pins one exact tag, so it is an install-time channel, not the pin: the range above stays the declared dependency — the built wheel carries Requires-Dist: llm-ingestion-guard<0.3,>=0.2 — and resolves normally once the package index exists. The optional [extract] extra (pdf/docx/xlsx parsers) is not populated yet. The planned Node half targets Node/ESM with zero npm dependencies.

License

MIT — see LICENSE.