# 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).** `/` for an agent or tool, `human:`, `process:`. 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/", 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 1–3 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 1–3 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.