llm-ingestion-okf/docs/plan/okf-v0.2-alignment.md
Kjell Tore Guttormsen ca453b2895 docs(okf-v0.2): the alignment track lands before the contract freezes
Everything built so far targets OKF v0.1. Upstream published v0.2 on
2026-07-25, so the plan records how the library relates to it and, more
importantly, who owns each decision.

Read from the spec itself rather than secondhand, which corrected two
readings that a summary had gotten wrong:

- STRICT_V1's `timestamp` is NOT a defect. Section 13.1 grants consumers a
  documented fallback to legacy `timestamp` precisely when `generated` is
  absent, and STRICT_V1 emits no `generated`. Nothing is asked of the wiki.
- The one measured shape problem is DEFAULT's `generated: "true"`, because
  v0.2 requires `generated.by` within `generated`. That key was not reserved
  in v0.1, so it was legal when written; v0.2 claimed the name. DEFAULT
  states commons' ingest-spec section 5 layer, so the fix is commons' call
  and is raised there as open question V1 rather than patched locally.

Two findings shrink the work. The canonical form for `generated` and a
single `verified` is an inline flow mapping, which the existing scalar
parser already round-trips as an opaque string, so block-list support is
only needed for `sources` and multi-verifier `verified` -- and only if a
named consumer asks. And the collision degrades safely: `_is_ingest_owned`
returns False for a v0.2 mapping, so a foreign concept is refused rather
than overwritten.

The track sits between Phases 3 and 4 because Phase 4 freezes the
cross-runtime contract. Freezing a v0.1 shape into two runtimes would let
the shared fixture suite certify the drift instead of catching it.

Self-imposed rule, since the spec does not require it: conform first, claim
after. Declaring `okf_version: "0.2"` is a MAY with no conformance
checkpoint, so claiming it early would be permitted -- and would be the same
class of true-sounding misleading claim as reporting a 0.2.0 measurement
under a 0.3.1 heading.

Also moves the guard 0.3.1 measurement procedure out of session state and
into execution-order.md, where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
2026-07-26 11:42:37 +02:00

12 KiB
Raw Blame History

OKF v0.2 alignment — plan

Status: decisions open, no code approved. This is a policy track, not a roadmap phase: it decides how the library relates to upstream OKF v0.2 and where each decision is owned. Everything built so far targets v0.1.

Read with: docs/plan/execution-order.md (where this track sits in the sequence), docs/plan/phase-3-configurable-contract.md (the profile object that carries the outcome), docs/phase-3-split-table.md (the profile/guard/consumer column discipline this track reuses).

Provenance of the facts below

Read from GoogleCloudPlatform/knowledge-catalog, okf/SPEC.md at main on 2026-07-26 — a branch, not a tag. First step of any implementation is to re-read the spec at a pinned commit and record it, exactly as assumption C2 pins the proving consumer's validator to 9ee5a8e. An enumeration read off a moving branch is a premise, not a fact.

What v0.2 actually changes

Two supersessions — the only backward-breaking changes, and both carry a documented consumer fallback (§13.1):

v0.1 v0.2 Fallback the spec grants
timestamp generated.at Consumers MAY fall back to legacy timestamp when generated is absent
body # Citations list sources frontmatter Consumers SHOULD read sources, MAY still parse legacy # Citations

Additive, all optional (§5): sources (per-source credibility signals — author, usage_count, last_modified, framed by usage_window), generated { by, at }, verified [ { by, at } ], status (draft/stable/deprecated, absent means stable), stale_after (absolute YYYY-MM-DD).

Also new:

  • Actor convention (§7). <producer>/<version> for an agent or tool, human:<id>, process:<id>. Consumers that classify trust key off the human: prefix, so producers MUST use it for hand-authored or human-confirmed content. Trust tiers (unverified / machine-confirmed / human-reviewed) are derived advisory signals, never a stored field.
  • Attested Computation concept type, with runtime, parameters, computation, executor, attester. The receipt and verdict wire formats and the full runtime protocol are explicitly deferred to a future revision.
  • okf_version stays root-index.md-only and is a MAY: a declaration of the version a bundle targets, with no conformance checkpoint tying the declaration to compliance.
  • Consumer tolerance is unchanged and broad. A consumer MUST NOT reject a bundle for missing optional fields, unknown type values, unknown frontmatter keys, broken cross-links, or missing index.md. One additional MUST: a bare verified mapping must be read as a one-element list.

One YAML detail that decides the parser scope: the canonical form for generated and a single verified is an inline flow mapping on one line (generated: { by: …, at: … }). Only a multi-verifier verified and sources are block lists.

Exposure audit — measured against the tree, not reasoned

Surface Status under v0.2 Decision owner
DEFAULT emits generated: "true" (materialize.py:164, inbox.py:127) Malformed for a v0.2 field: generated.by is REQUIRED within generated. generated was not reserved in v0.1, so this was legal when written — v0.2 claimed the name commons (ingest-spec §5)
_is_ingest_owned requires generated == "true" (materialize.py:143-149) Fail-safe. A v0.2 mapping is not the string "true", so the predicate returns False → target not ours → collision → refuse to overwrite. No safety hole this repo
STRICT_V1 emits timestamp, and no generated Conformant via the §13.1 legacy fallback, which applies precisely because generated is absent. No action is forced on the wiki wiki
parse_frontmatter is scalar-only (partition(":")dict[str, str]) Reads an inline flow mapping as an opaque string — round-trip-safe. Cannot read block lists, so sources and multi-verifier verified are unreachable this repo
Fixtures and tests pin okf_version: 0.1 Accurate today: a MAY-field target declaration catalog (decision E1: okf_version tracks the upstream Google version)
catalog's gate checks okf_version on form (/^\d+(\.\d+)*$/), not membership "0.2" passes unchanged — an upstream bump breaks no gate catalog

Two results worth stating plainly, because both cut against the first reading:

  1. STRICT_V1's timestamp is not broken. It sits on the documented legacy path. The earlier framing of it as a required-field defect was wrong.
  2. DEFAULT is the only surface with a real shape problem, and it is the one surface this repo may not unilaterally change: DEFAULT states the ingest-spec §5 layer, whose authorship is commons'. Patching it locally is an existing non-goal.

Note the interaction that makes (1) conditional: the timestamp fallback is granted only when generated is absent. Any profile that emits both a timestamp and a malformed generated would have neither a valid generated.at nor an eligible fallback. No current profile does this — DEFAULT has no timestamp and STRICT_V1 has no generated — and no profile should be allowed to reach that combination.

The sequencing rule we impose on ourselves

Conform first, claim after. Do not move a bundle's okf_version to "0.2" before the bundle's shape satisfies what it claims.

The spec does not require this — §12 is a MAY with no conformance checkpoint attached, so declaring "0.2" early would be permitted. We adopt the rule anyway, for the same reason as measure first, widen after on the guard pin: a version claim that is literally permitted but not earned is the true-sounding misleading claim, and that class of mistake has already cost this repo a session.

Open questions — to answer before any code

Numbered like the phase-3 assumptions so they can be cited in review.

V1 — Does generated change shape in DEFAULT, and may it? This is the central question and the answer is not ours alone: DEFAULT is commons' §5 layer. Three directions to put to commons, with our recommendation:

  • (a) Leave generated: "true"; document the deviation. Cheapest; keeps emitting a v0.2 field with a v1-era value.
  • (b) Recommended. Migrate to generated: { by: "llm-ingestion-okf/<version>", at: <ingested_at> } and derive the machine-generated predicate from generated.by instead of a bespoke boolean. This is the spec-native answer, removes a hand-rolled key, and yields a real generated.at. at binds to the already-validated required ingested_at argument, so no wall-clock default is introduced and determinism holds. Costs: a _is_ingest_owned change and a golden-fixture regeneration.
  • (c) Rename our marker to a non-reserved key and leave generated free for v0.2 semantics. Preserves the predicate exactly; still a fixture regeneration.

V2 — How far does the frontmatter parser need to go? Two tiers, and only the first is cheap:

  • Tier 1, inline flow mappings: already round-trip as opaque strings. This needs a characterization test, not a change.
  • Tier 2, block lists (sources, multi-verifier verified): real work.

The question is whether any named consumer requires Tier 2 today. If none does, Tier 2 stays an extension point — the inverse of fields without a reader are not written: do not build a parser for a field no profile reads.

V3 — Do we add v0.2 trust fields to any profile at all? Default answer no, until a named consumer asks. Neither wiki nor catalog has. status, stale_after, verified, and sources are all optional upstream, and adding them speculatively is the speculative-abstraction anti-pattern that phase 3 exists to avoid.

V4 — What value does okf_version carry, and who sets it? Already recorded as policy-not-constant. E1 gives the decision to catalog. This repo's obligation is narrower and testable: no profile may hard-code 0.1, and the root-frontmatter policy must express any value.

V5 — Is Attested Computation in scope? Recommended out, on two independent grounds. Upstream has deferred the receipt and verdict wire formats, so building now means porting an unfrozen contract — the precise failure mode execution-order.md warns about for the Node half. And attestation ("did this run produce the value the sanctioned way") sits against the guard's boundary rather than this library's plumbing, so the boundary question goes to the operator before any design.

TDD order — when quota returns

Steps 13 are characterization and audit only: they add tests, change no behavior, and are safe to run before V1 is answered.

  1. Characterize what already holds. parse_frontmatter round-trips an inline flow mapping opaquely; _is_ingest_owned returns False for a v0.2 generated mapping. The second test converts today's fail-safe from an accident into a documented guarantee.
  2. Audit Door C against the tolerance rules — one test per consumer MUST NOT (unknown type, unknown key, broken cross-link, missing index.md) plus the bare-verified-as-one-element-list MUST. Measured, not reasoned: Door C is a consumer in the OKF sense and either honours these or does not.
  3. Assert no hard-coded 0.1 anywhere a profile should decide (V4).
  4. Only after commons answers V1: the generated migration, its _is_ingest_owned change, and the golden-fixture regeneration — as its own release with its own CHANGELOG entry, never folded into another change.

Key assumptions, each with its test

# Assumption Test
V-A1 The spec enumeration above is complete Re-read okf/SPEC.md at a pinned commit, record the commit, diff against this doc before step 4
V-A2 Inline flow mappings survive our parser unmodified Step 1 characterization test; byte-exact round-trip
V-A3 v0.2 input cannot cause an unowned overwrite Step 1 test: _is_ingest_owned False on a generated mapping
V-A4 Door C meets the v0.2 consumer tolerance rules Step 2, one test per MUST NOT — a failure here is a real defect, not a plan revision
V-A5 No profile hard-codes an upstream version Step 3
V-A6 The generated migration is byte-neutral outside frontmatter Golden diff confined to the frontmatter block; every other byte identical

Non-goals

  • Implementing Attested Computation, executors, attesters, receipts, or verdicts (V5).
  • Adding any optional v0.2 family to a profile without a named consumer requesting it (V3).
  • Editing commons' ingest-spec locally. V1 is raised in commons; if commons declines, the deviation is documented rather than patched here.
  • Moving okf_version to "0.2" in any fixture or profile before the shape earns it, and never without catalog.
  • Any change to the wiki's timestamp. It is on the documented legacy path; the wiki is informed, not asked.

Coordination — who needs to know what

Notification set, not an approval queue. Each entry names the one thing that repo owns:

  • commons — V1. The only blocking dependency: generated's shape in the ingest-spec §5 layer.
  • catalog — form owner. That upstream moved to v0.2, that its form-not- membership gate absorbs the bump with no change, and that V4 is theirs.
  • wiki — informational. Their timestamp is legacy-but-readable under §13.1; no action is required of them, and the earlier reading that called it a defect was wrong.
  • okr — before the reference-implementation lift, so the Node half is not ported against a v0.1-shaped contract.

Verification

  1. Spec re-read at a pinned commit; commit hash recorded in this doc.
  2. pytest green; mypy --strict src/; ruff check . and ruff format --check . clean.
  3. Steps 13 add tests and change zero production behavior: golden suite byte-for-byte, git diff --stat examples/ empty.
  4. V1 answered in writing by commons before step 4 begins.
  5. If step 4 lands: golden diff confined to the frontmatter block; boundary grep-gate still empty; its own release and CHANGELOG entry.