docs(okf-v0.2): supporting the latest OKF version becomes standing policy

Operator directive 2026-07-26: the library always supports the current
latest version of Google OKF. v0.2 shipped 2026-07-25, so v0.2 support is
committed work rather than something a consumer has to request. This
overrides the previous default answer to open question V3 ("no until a
named consumer asks"), which is kept in the plan marked superseded so the
override reads as deliberate.

Support is additive: a new profile, never a migration of the existing two.
That single design choice is what makes an always-latest policy sustainable,
and it resolves the tension the directive would otherwise create with three
constraints that do not yield to it:

- DEFAULT states commons' ingest-spec section 5 layer, so its `generated`
  shape is commons' call. Under the additive design this stops blocking us,
  which takes commons off the critical path.
- STRICT_V1 mirrors the proving consumer's ratified contract; changing
  another repo's contract from here would violate O2.
- v0.2 defers the attestation receipt and verdict wire formats upstream, so
  the format is supportable and the unspecified runtime is not. It re-enters
  scope when upstream specifies it.

This is also the first time the phase-3 profile abstraction is forced by
something outside this repo rather than by a second consumer, which is the
better test of whether the seam was cut in the right place.

Deliverables D1-D6 replace the earlier decision-round framing: a frontmatter
model that can carry block lists (`sources`, multi-verifier `verified`), an
OKF_V0_2 profile plus an OKF_LATEST alias whose moving-target tradeoff is
documented rather than hidden, Door C conformance against the consumer
tolerance rules, `Attested Computation` round-trip, v0.2 golden fixtures, and
a release-checklist re-check so the standing policy cannot decay silently.

Two new assumptions carry the weight. V-A7 forbids any profile from emitting
`timestamp` together with a malformed `generated`, since that combination
would have neither a valid `generated.at` nor an eligible section 13.1
fallback. V-A8 validates our own v0.2 fixture against upstream's reference
implementation, because every other test in the suite only asks whether we
agree with ourselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
This commit is contained in:
Kjell Tore Guttormsen 2026-07-26 13:41:30 +02:00
commit cb23de4700
3 changed files with 257 additions and 171 deletions

View file

@ -63,14 +63,29 @@ are the library baseline. First consumer: `portfolio-optimiser-claude`.
The two halves share the OKF contract and fixture suite, **not code**. The two halves share the OKF contract and fixture suite, **not code**.
**Upstream version: everything above targets OKF v0.1.** Google published v0.2 ### Upstream version policy (standing, non-negotiable)
on 2026-07-25 (two supersessions, both with documented consumer fallbacks; all
new fields optional). Alignment is a policy track between Phases 3 and 4, not a **The library always supports the current latest version of Google OKF.** Set by
phase: `docs/plan/okf-v0.2-alignment.md`. Two constraints bind here — the the operator 2026-07-26. Phases 13 were built against v0.1; v0.2 shipped
`generated` marker `DEFAULT` emits sits in commons' ingest-spec §5 layer, so its 2026-07-25, so v0.2 support is committed work — not contingent on a consumer
shape is commons' call, not ours; and `okf_version`'s value belongs to catalog asking for it. Plan: `docs/plan/okf-v0.2-alignment.md`.
(decision E1). No profile may hard-code an upstream version, and no bundle
claims a version its shape has not earned. Support is **additive, expressed as a new profile**, never a migration of
existing ones. This is what makes the policy sustainable instead of a recurring
crisis, and it is bounded by three facts that do not yield to it:
- `DEFAULT` states commons' ingest-spec §5 layer — its `generated` shape is
commons' call, raised there, never patched locally.
- `STRICT_V1` mirrors the proving consumer's ratified contract — changing another
repo's contract from here violates O2.
- `okf_version`'s *value* belongs to catalog (decision E1).
Two invariants fall out: no profile hard-codes an upstream version, and no bundle
declares a version its shape has not earned. Where upstream itself defers a
contract — v0.2's attestation receipt and verdict wire formats — the format is
supported and the unspecified runtime is not; it re-enters scope when upstream
specifies it. Because "always latest" decays silently, the release checklist
carries an upstream-version re-check.
Phase 4 preconditions (coordination, not unilateral moves): Phase 4 preconditions (coordination, not unilateral moves):
- Lifts okr's reference implementations (`okf-check.mjs`, `okf-index.mjs`, - Lifts okr's reference implementations (`okf-check.mjs`, `okf-index.mjs`,

View file

@ -100,30 +100,37 @@ policy differs per consumer and becomes configurable in Phase 3." This is where
the contract becomes explicit and frozen — the precondition for a clean Node the contract becomes explicit and frozen — the precondition for a clean Node
port. port.
## OKF v0.2 alignment — decisions between 3 and 4, code possibly later ## OKF v0.2 support — between 3 and 4, and it is real code
Everything through Phase 3 targets OKF v0.1. Upstream published v0.2 on Everything through Phase 3 targets OKF v0.1. Upstream published v0.2 on
2026-07-25. Detail and open questions: `docs/plan/okf-v0.2-alignment.md`. 2026-07-25, and the standing operator policy is that the library always supports
the current latest OKF version (see `CLAUDE.md`). So this is a committed
implementation track, not a decision round:
`docs/plan/okf-v0.2-alignment.md`.
**Why the decisions must land before Phase 4 code, even if the code does not.** **Why it must land before Phase 4 code.** Phase 4 ports a *frozen, explicit*
Phase 4 ports a *frozen, explicit* contract and uses the shared fixture suite as contract and uses the shared fixture suite as the cross-runtime conformance
the cross-runtime conformance oracle. Freezing that contract while its relation oracle. Freezing that contract before v0.2 is supported means freezing a v0.1
to upstream v0.2 is undecided means freezing a v0.1 shape into two runtimes shape into two runtimes instead of one, and the fixture suite would then certify
instead of one, and the fixture suite would then certify the drift rather than the drift rather than catch it. Worse, the Node half would need the same v0.2
catch it. The expensive version of this mistake is discovered after the Node half work done twice, in two languages, against a contract that had already been
exists; the cheap version is a decision recorded now. declared final. The expensive version of this mistake is discovered after the
Node half exists.
**Why it is not a phase.** v0.2's breaking surface is two supersessions, both **Why it is not a phase of its own.** It adds no new door and no new stage — it
with documented consumer fallbacks, and every new field is optional. The only adds a *profile*, which is precisely what Phase 3 built the seam for. Support is
measured shape problem is `DEFAULT`'s `generated` marker, which lives in commons' additive: `DEFAULT` (commons') and `STRICT_V1` (the wiki's) keep their contracts
ingest-spec §5 layer and is therefore not this repo's to change. So the work is untouched, and v0.2 arrives as a third profile plus an `OKF_LATEST` alias. This
mostly *deciding and routing*, expressed through the Phase 3 profile object — is the first time the Phase 3 abstraction is forced by something outside this
not a new implementation stage. repo rather than by a second consumer, which is the better test of it.
**Order inside the track.** Characterization and audit first (they add tests and **Order inside the track.** Characterization and audit first — they add tests and
change no behavior, so they are safe while the central question is still open), change no behavior. Then the frontmatter model that can carry block lists
then the one blocking dependency — commons' answer on `generated` — and only then (`sources`, multi-verifier `verified`), then the profile, then the golden
any migration, as its own release. fixtures that make the support claim testable. The `okf_version: "0.2"`
declaration comes last, once the shape has earned it. Commons' answer on
`DEFAULT`'s `generated` runs in parallel and no longer blocks: the additive
design took it off the critical path.
## Phase 4 — Node half (two clocks) ## Phase 4 — Node half (two clocks)

View file

@ -1,36 +1,59 @@
# OKF v0.2 alignment — plan # OKF v0.2 support — plan
Status: **decisions open, no code approved.** This is a policy track, not a Status: **committed implementation track.** Standing policy (operator,
roadmap phase: it decides how the library relates to upstream OKF v0.2 and where 2026-07-26): *the library always supports the current latest version of Google
each decision is owned. Everything built so far targets v0.1. OKF.* v0.2 is out, so v0.2 support is committed work — not conditional on a
consumer requesting it.
Read with: `docs/plan/execution-order.md` (where this track sits in the Read with: `docs/plan/execution-order.md` (sequencing),
sequence), `docs/plan/phase-3-configurable-contract.md` (the profile object that `docs/plan/phase-3-configurable-contract.md` (the profile object that carries
carries the outcome), `docs/phase-3-split-table.md` (the profile/guard/consumer this), `docs/phase-3-split-table.md` (the profile/guard/consumer column
column discipline this track reuses). discipline).
## What "support the latest version" means here
It means **additive**: the library can read, validate, materialize, and index
v0.2-shaped bundles. It does **not** mean migrating existing profiles onto v0.2.
That distinction is the whole design, and it is what makes a standing
always-latest policy implementable rather than a recurring crisis:
- `DEFAULT` states commons' ingest-spec §5 layer. Its shape is commons'
authorship, and editing it locally is a standing non-goal.
- `STRICT_V1` mirrors the proving consumer's contract, ratified by their operator
on 2026-07-25. Changing another repo's contract from here violates O2.
- So v0.2 support arrives as **a new profile**, alongside the existing two. The
phase-3 profile object is exactly the seam that makes this possible; this is
the first time that abstraction pays for itself against an external forcing
function rather than a second consumer.
The upper bound is set upstream, not by ambition: v0.2 **defers the receipt and
verdict wire formats and the full attestation runtime protocol to a future
revision**. The *format* is fully supportable; an unspecified runtime is not.
See D4.
## Provenance of the facts below ## Provenance of the facts below
Read from `GoogleCloudPlatform/knowledge-catalog`, `okf/SPEC.md` at **`main` on 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 2026-07-26** — a branch, not a tag. **First implementation step is to re-read the
re-read the spec at a pinned commit and record it**, exactly as assumption C2 spec at a pinned commit and record it**, exactly as assumption C2 pins the
pins the proving consumer's validator to `9ee5a8e`. An enumeration read off a proving consumer's validator to `9ee5a8e`. An enumeration read off a moving
moving branch is a premise, not a fact. branch is a premise, not a fact.
## What v0.2 actually changes ## What v0.2 actually changes
Two supersessions — the only backward-breaking changes, and both carry a Two supersessions — the only backward-breaking changes, both with a documented
documented consumer fallback (§13.1): consumer fallback (§13.1):
| v0.1 | v0.2 | Fallback the spec grants | | v0.1 | v0.2 | Fallback the spec grants |
|---|---|---| |---|---|---|
| `timestamp` | `generated.at` | Consumers **MAY** fall back to legacy `timestamp` **when `generated` is absent** | | `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` | | body `# Citations` list | `sources` frontmatter | Consumers **SHOULD** read `sources`, **MAY** still parse legacy `# Citations` |
Additive, all optional (§5): `sources` (per-source credibility signals — Additive, all optional upstream (§5) — and all in scope for us under the standing
`author`, `usage_count`, `last_modified`, framed by `usage_window`), `generated policy: `sources` (per-source credibility signals — `author`, `usage_count`,
{ by, at }`, `verified [ { by, at } ]`, `status` (`draft`/`stable`/`deprecated`, `last_modified`, framed by `usage_window`), `generated { by, at }`,
absent means `stable`), `stale_after` (absolute `YYYY-MM-DD`). `verified [ { by, at } ]`, `status` (`draft`/`stable`/`deprecated`, absent means
`stable`), `stale_after` (absolute `YYYY-MM-DD`).
Also new: Also new:
@ -40,8 +63,7 @@ Also new:
human-confirmed content. Trust tiers (unverified / machine-confirmed / human-confirmed content. Trust tiers (unverified / machine-confirmed /
human-reviewed) are *derived* advisory signals, never a stored field. human-reviewed) are *derived* advisory signals, never a stored field.
- **`Attested Computation`** concept type, with `runtime`, `parameters`, - **`Attested Computation`** concept type, with `runtime`, `parameters`,
`computation`, `executor`, `attester`. The receipt and verdict wire formats and `computation`, `executor`, `attester`.
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 - **`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 the version a bundle *targets*, with no conformance checkpoint tying the
declaration to compliance. declaration to compliance.
@ -50,10 +72,11 @@ Also new:
keys, broken cross-links, or missing `index.md`. One additional MUST: a bare keys, broken cross-links, or missing `index.md`. One additional MUST: a bare
`verified` mapping must be read as a one-element list. `verified` mapping must be read as a one-element list.
One YAML detail that decides the parser scope: the canonical form for `generated` One YAML detail that sizes the parser work: the canonical form for `generated`
and a single `verified` is an **inline flow mapping** on one line and a single `verified` is an **inline flow mapping** on one line
(`generated: { by: …, at: … }`). Only a multi-verifier `verified` and `sources` (`generated: { by: …, at: … }`), which the existing scalar parser already
are block lists. round-trips as an opaque string. Only a multi-verifier `verified` and `sources`
are **block lists**, and those are what force a richer frontmatter model.
## Exposure audit — measured against the tree, not reasoned ## Exposure audit — measured against the tree, not reasoned
@ -62,151 +85,192 @@ are block lists.
| `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) | | `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 | | `_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 | | `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 | | `parse_frontmatter` is scalar-only (`partition(":")``dict[str, str]`) | Inline flow mappings round-trip as opaque strings. Block lists are unreachable, so `sources` and multi-verifier `verified` need a richer model — **now in scope** (D1) | 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) | | Fixtures and tests pin `okf_version: 0.1` | Accurate for the v0.1 profiles. The new v0.2 profile declares `"0.2"` | **catalog** (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 | | 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: Two results that cut against the first reading of the change:
1. **`STRICT_V1`'s `timestamp` is not broken.** It sits on the documented legacy 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. path. An 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 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 surface this repo may not unilaterally change. Under the additive design this
ingest-spec §5 layer, whose authorship is commons'. Patching it locally is an stops being on our critical path (D2, V1).
existing non-goal.
Note the interaction that makes (1) conditional: the `timestamp` fallback is The interaction that keeps (1) true and must never be violated: the `timestamp`
granted only when `generated` is **absent**. Any profile that emits both a fallback is granted only when `generated` is **absent**. A profile emitting both
`timestamp` and a malformed `generated` would have neither a valid `generated.at` a `timestamp` and a malformed `generated` would have neither a valid
nor an eligible fallback. No current profile does this — `DEFAULT` has no `generated.at` nor an eligible fallback. No current profile does this — `DEFAULT`
`timestamp` and `STRICT_V1` has no `generated` — and no profile should be allowed has no `timestamp`, `STRICT_V1` has no `generated` — and no profile may be
to reach that combination. allowed to reach that combination. This becomes a profile-construction invariant
(V-A7).
## The sequencing rule we impose on ourselves ## The sequencing rule we impose on ourselves
**Conform first, claim after.** Do not move a bundle's `okf_version` to `"0.2"` **Conform first, claim after.** No bundle declares an `okf_version` its shape has
before the bundle's shape satisfies what it claims. not earned.
The spec does not require this — §12 is a MAY with no conformance checkpoint The spec does not require this — §12 is a MAY with no conformance checkpoint, so
attached, so declaring `"0.2"` early would be permitted. We adopt the rule declaring `"0.2"` early would be permitted. We adopt the rule anyway, for the
anyway, for the same reason as *measure first, widen after* on the guard pin: a same reason as *measure first, widen after* on the guard pin: a version claim
version claim that is literally permitted but not earned is the true-sounding that is literally permitted but not earned is the true-sounding misleading claim,
misleading claim, and that class of mistake has already cost this repo a session. and that class of mistake has already cost this repo a session. Concretely, the
v0.2 profile declares `"0.2"` only once D1D3 and their fixtures are green.
## Open questions — to answer before any code ## Deliverables
Numbered like the phase-3 assumptions so they can be cited in review. **D1 — A frontmatter model that carries block lists.** The single largest piece,
and the one that must be designed rather than improvised: `parse_frontmatter`
returns `dict[str, str]` today, and `sources` / multi-verifier `verified` cannot
be expressed in it. Three directions:
**V1 — Does `generated` change shape in `DEFAULT`, and may it?** - (a) Widen the value type in place (`str | Mapping | list`). Cheapest to write,
This is the central question and the answer is *not ours alone*: `DEFAULT` is worst to consume: every existing caller must newly narrow, and `mypy --strict`
commons' §5 layer. Three directions to put to commons, with our recommendation: makes that cost visible everywhere at once.
- (b) A typed frontmatter model for all profiles. Cleanest end state, but it
rewrites the v0.1 read path and puts C1 (default-profile behavior neutrality)
at risk for no v0.1 benefit.
- (c) **Recommended.** Keep the scalar reader as the v0.1 path, untouched, and
add a structured reader selected by the profile. C1 stays trivially true
(`DEFAULT` and `STRICT_V1` do not change code paths at all), the new
complexity is reachable only from the new profile, and the two readers share
the same byte-level guarantees.
- (a) Leave `generated: "true"`; document the deviation. Cheapest; keeps emitting **D2 — An `OKF_V0_2` profile.** Frontmatter schema covering the §5 families;
a v0.2 field with a v1-era value. `generated: { by: "llm-ingestion-okf/<version>", at: <ingested_at> }` using the
- (b) **Recommended.** Migrate to §7 actor convention; `sources` emission from the manifest's source; `status` and
`generated: { by: "llm-ingestion-okf/<version>", at: <ingested_at> }` and `stale_after` expressible; root-index `okf_version: "0.2"`.
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?** Also an `OKF_LATEST` alias, which is how the standing policy becomes visible in
Two tiers, and only the first is cheap: code. **Document the tradeoff rather than hiding it:** an alias that moves means
a consumer bound to `OKF_LATEST` inherits upstream's breaking changes on a
library upgrade. The versioned constants are the stable binding and are what
consumers should pin; `OKF_LATEST` is for callers who have explicitly opted into
tracking.
- Tier 1, inline flow mappings: already round-trip as opaque strings. This needs `at` binds to the already-validated required `ingested_at` argument, so no
a **characterization test**, not a change. wall-clock default is introduced and determinism holds.
- Tier 2, block lists (`sources`, multi-verifier `verified`): real work.
The question is whether any named consumer requires Tier 2 today. If none does, **D3 — Door C accepts any v0.2 bundle.** This is where "we support v0.2" is most
Tier 2 stays an extension point — the inverse of *fields without a reader are not user-visible: the §14 consumer tolerance MUST NOTs, plus the bare-`verified`
written*: do not build a parser for a field no profile reads. coercion. Measured with one test per rule — Door C is a consumer in the OKF
sense and either honours these or does not.
**V3 — Do we add v0.2 trust fields to any profile at all?** **D4 — `Attested Computation`: format yes, runtime no.** Support the type and its
Default answer **no**, until a named consumer asks. Neither wiki nor catalog has. fields (`runtime`, `parameters`, `computation`, `executor`, `attester`) for
`status`, `stale_after`, `verified`, and `sources` are all optional upstream, and parse, validate, round-trip, and index. Do **not** implement execution or
adding them speculatively is the speculative-abstraction anti-pattern that attestation: the receipt and verdict wire formats are explicitly deferred
phase 3 exists to avoid. upstream, so there is no contract to build against, and "did this run produce the
value the sanctioned way" sits against the guard's boundary rather than this
library's plumbing. When upstream specifies the protocol, the standing policy
brings it back into scope — and the boundary question goes to the operator first.
**V4 — What value does `okf_version` carry, and who sets it?** **D5 — v0.2 golden fixtures.** A byte-exact v0.2 bundle in `examples/`. This is
Already recorded as policy-not-constant. E1 gives the decision to catalog. This what converts "we support v0.2" from a claim into a test, and in phase 4 it
repo's obligation is narrower and testable: no profile may hard-code `0.1`, and becomes the cross-runtime conformance oracle for the Node half too.
the root-frontmatter policy must express any value.
**V5 — Is `Attested Computation` in scope?** **D6 — Keep the policy from decaying.** "Always latest" silently rots unless
Recommended **out**, on two independent grounds. Upstream has deferred the something checks. Two mechanisms, both cheap: the spec commit is pinned and
receipt and verdict wire formats, so building now means porting an unfrozen recorded in this doc (V-A1), and the release checklist gains one item — re-check
contract — the precise failure mode `execution-order.md` warns about for the Node upstream for a newer OKF version and record the result, even when unchanged. A
half. And attestation ("did this run produce the value the sanctioned way") sits standing policy with no detection mechanism is an intention, not a policy.
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 ## Open questions
Steps 13 are characterization and audit only: they add tests, change no **V1 — `generated`'s shape in `DEFAULT` (commons').** *No longer blocks our v0.2
behavior, and are safe to run before V1 is answered. support*, because D2 puts v0.2 in a new profile. Still raised with commons,
because `DEFAULT` keeps emitting a v0.2-reserved key with a v1-era value.
Recommendation to carry: `generated: { by: "llm-ingestion-okf/<v>", at:
<ingested_at> }`, deriving the machine-generated predicate from `generated.by`
instead of a bespoke boolean — spec-native, removes a hand-rolled key, costs a
`_is_ingest_owned` change and a fixture regeneration. If commons declines, the
deviation is documented, not patched here.
**V2 — Which D1 direction?** (c) recommended above; needs a decision before code
because it determines whether `mypy --strict` churn lands across the existing
call sites or stays confined to the new path.
**V3 — Do we add v0.2 families at all?** **Answered: yes**, by the standing
policy. Superseded — kept here to record that the earlier default answer ("no
until a named consumer asks") was overridden deliberately, not forgotten.
**V4 — `okf_version` values.** Policy, not constant. E1 gives the value to
catalog. Our obligation is narrower and testable: no profile hard-codes a
version, and the root-frontmatter policy expresses any value.
**V5 — Attestation scope.** Answered by D4: format in, runtime out, on upstream's
own deferral rather than our preference.
## TDD order
Steps 13 add tests and change no behavior, so they are safe first and de-risk
everything after.
1. **Characterize what already holds.** `parse_frontmatter` round-trips an inline 1. **Characterize what already holds.** `parse_frontmatter` round-trips an inline
flow mapping opaquely; `_is_ingest_owned` returns False for a v0.2 `generated` 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 mapping. The second converts today's fail-safe from an accident into a
documented guarantee. documented guarantee.
2. **Audit Door C against the tolerance rules** — one test per consumer MUST NOT 2. **Audit Door C against the tolerance rules** (D3): one test per MUST NOT plus
(unknown `type`, unknown key, broken cross-link, missing `index.md`) plus the the bare-`verified` MUST. Failures here are real defects, not plan revisions.
bare-`verified`-as-one-element-list MUST. Measured, not reasoned: Door C is a 3. **Assert no hard-coded version** anywhere a profile should decide (V4), and
consumer in the OKF sense and either honours these or does not. the no-`timestamp`-plus-malformed-`generated` construction invariant (V-A7).
3. **Assert no hard-coded `0.1`** anywhere a profile should decide (V4). 4. **D1** — the structured reader, behind the profile seam, with the v0.1 scalar
4. **Only after commons answers V1:** the `generated` migration, its path untouched and the full existing suite as the net.
`_is_ingest_owned` change, and the golden-fixture regeneration — as its own 5. **D2** — the `OKF_V0_2` profile and `OKF_LATEST` alias.
release with its own CHANGELOG entry, never folded into another change. 6. **D5** — v0.2 golden fixtures; `okf_version: "0.2"` is declared only here,
once 15 are green (conform first, claim after).
7. **D4**`Attested Computation` as a type with round-trip fidelity.
8. **V1 to commons** in parallel throughout; **D6** lands with the release.
## Key assumptions, each with its test ## Key assumptions, each with its test
| # | Assumption | 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-A1 | The spec enumeration here 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-A2 | Inline flow mappings survive the scalar parser unmodified | Step 1 characterization; 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-A3 | v0.2 input cannot cause an unowned overwrite | Step 1: `_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-A4 | Door C meets the v0.2 consumer tolerance rules | Step 2, one test per MUST NOT |
| V-A5 | No profile hard-codes an upstream version | Step 3 | | 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 | | V-A6 | Adding v0.2 support is behavior-neutral for v0.1 profiles | Golden suite byte-for-byte under `DEFAULT` **and** `STRICT_V1`; existing tests unmodified and green (C1 extended) |
| V-A7 | No profile can emit `timestamp` together with a malformed `generated` | Named construction-time test, same shape as C3's verdict reservation |
| V-A8 | A v0.2 bundle we emit is accepted by an independent v0.2 consumer | Validate the D5 fixture against upstream's reference implementation, not only against our own reader |
V-A8 is the one that keeps this honest. Every other test asks whether we agree
with ourselves.
## Non-goals ## Non-goals
- Implementing `Attested Computation`, executors, attesters, receipts, or - Implementing attestation execution — executors, attesters, receipts, verdicts
verdicts (V5). (D4, on upstream's deferral).
- Adding any optional v0.2 family to a profile without a named consumer - Migrating `DEFAULT` or `STRICT_V1` onto v0.2. Support is additive; those two
requesting it (V3). contracts belong to commons and the wiki.
- Editing commons' ingest-spec locally. V1 is raised in commons; if commons - Editing commons' ingest-spec locally. V1 is raised there.
declines, the deviation is documented rather than patched here. - Declaring `okf_version: "0.2"` on any bundle whose shape has not earned it.
- Moving `okf_version` to `"0.2"` in any fixture or profile before the shape - Config-file/DSL profile loading — still a phase-3 extension point.
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 ## Coordination — who needs to know what
Notification set, not an approval queue. Each entry names the one thing that - **commons** — V1. `generated`'s shape in ingest-spec §5. No longer blocking us,
repo owns: still theirs to decide.
- **catalog** — form owner. Upstream moved to v0.2; their form-not-membership
- **commons** — V1. The only blocking dependency: `generated`'s shape in the gate absorbs the bump with no change; V4 is theirs; and a new profile now
ingest-spec §5 layer. declares `"0.2"`, which is the first bundle shape in this repo to do so.
- **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 - **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 §13.1; nothing is required of them, and the earlier reading that called it a
defect was wrong. defect was wrong.
- **okr** — before the reference-implementation lift, so the Node half is not - **okr** — before the reference-implementation lift, so the Node half ports a
ported against a v0.1-shaped contract. contract that already knows about v0.2 rather than a v0.1 shape.
## Verification ## Verification
1. Spec re-read at a pinned commit; commit hash recorded in this doc. 1. Spec re-read at a pinned commit; hash recorded in this doc.
2. `pytest` green; `mypy --strict src/`; `ruff check .` and 2. `pytest` green; `mypy --strict src/`; `ruff check .` and
`ruff format --check .` clean. `ruff format --check .` clean.
3. Steps 13 add tests and change zero production behavior: golden suite 3. V-A6: golden suite byte-for-byte under both existing profiles; zero fixture
byte-for-byte, `git diff --stat examples/` empty. changes outside the new v0.2 fixtures (`git diff --stat examples/` shows
4. V1 answered in writing by commons before step 4 begins. additions only).
5. If step 4 lands: golden diff confined to the frontmatter block; boundary 4. V-A7 and V-A5 named tests present and failing-by-construction if removed.
grep-gate still empty; its own release and CHANGELOG entry. 5. V-A8: the v0.2 fixture validates under upstream's reference implementation.
6. Boundary grep-gate still empty (`sanitize|quarantine|lexicon` absent outside
guard imports).
7. D6: release checklist contains the upstream-version re-check item.