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
This commit is contained in:
Kjell Tore Guttormsen 2026-07-26 11:42:37 +02:00
commit ca453b2895
4 changed files with 291 additions and 5 deletions

View file

@ -63,6 +63,15 @@ are the library baseline. First consumer: `portfolio-optimiser-claude`.
The two halves share the OKF contract and fixture suite, **not code**.
**Upstream version: everything above targets OKF v0.1.** Google published v0.2
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
phase: `docs/plan/okf-v0.2-alignment.md`. Two constraints bind here — the
`generated` marker `DEFAULT` emits sits in commons' ingest-spec §5 layer, so its
shape is commons' call, not ours; and `okf_version`'s value belongs to catalog
(decision E1). No profile may hard-code an upstream version, and no bundle
claims a version its shape has not earned.
Phase 4 preconditions (coordination, not unilateral moves):
- Lifts okr's reference implementations (`okf-check.mjs`, `okf-index.mjs`,
innboks libs) in agreement with okr and the marketplace catalog; the

View file

@ -8,12 +8,14 @@ own doc:
- `docs/plan/phase-1-door-a.md` — DONE, shipped at v0.3.2.
- `docs/plan/phase-2-doors-b-c.md`
- `docs/plan/phase-3-configurable-contract.md`
- `docs/plan/okf-v0.2-alignment.md` — a policy track, not a phase; slots between
Phases 3 and 4 for the reason given below.
- `docs/plan/phase-4-node-half.md`
Headline order: **Stage 0 (guard gate) → Phase 2 → Phase 3 → Phase 4 code**,
with Phase 2 split guard-independent-first and Phase 4's *coordination* started
in parallel from the beginning. The quality argument for this order is below —
it is not merely the roadmap numbering.
Headline order: **Stage 0 (guard gate) → Phase 2 → Phase 3 → OKF v0.2 alignment
decisions → Phase 4 code**, with Phase 2 split guard-independent-first and
Phase 4's *coordination* started in parallel from the beginning. The quality
argument for this order is below — it is not merely the roadmap numbering.
## Stage 0 — Guard readiness (gate; do first, cheap)
@ -33,6 +35,36 @@ persist-gate design must know now, not at the integration step.
Outcome recorded in STATE before Phase 2 code starts.
### Stage 0 revisited — measuring guard 0.3.1 before widening the pin
The pin held is `>=0.2,<0.3`, which can only ever resolve `v0.2.0`. The guard repo
gates its own 1.0 on our fixture suite being measured against `v0.3.1`, so this
measurement is owed outward and is that repo's only named blocker. Order is
*measure first, widen after*: widening the range before measuring would ship a
window admitting a version the fixtures never ran against.
Procedure — do not vary it, each step exists because of a specific failure mode:
1. Scratch venv **outside** `.venv`: install the package `--no-deps`, then the
guard from the `v0.3.1` tag. Do not touch `pyproject.toml`.
2. **Assert the resolved version** via `importlib.metadata` *before* running
anything, and carry it in the reported result. An unmodified tree resolves
`0.2.0` and runs green — a true claim about 0.2.0 published under a heading
that says 0.3.1. This step is what makes the result honest.
3. Run `tests/test_guard_adapter.py` (19 tests, real scanner) and the full suite.
Watch percent-escapes specifically: the slugger builds filenames from titles,
and the guard has recorded that as its most likely false positive. Report raw
URLs, not counts.
4. **Send the result whichever way it goes**, and state explicitly that 19
fixture tests are not a corpus — their gate is "does 0.3.1 change our
verdicts", not "how often does the false positive fire".
5. Green → one release: `>=0.2,<0.4`, uv tag `v0.3.1`, CLAUDE.md and README
updated together. Red → no bump, and the finding goes back to the guard.
Until this lands, consumers of v0.4.0 resolve guard 0.2.0 (`pyproject.toml:25`).
That is a silent under-defend: no build fails, so nothing surfaces it. The
release in step 5 is what closes it.
## Phase 2 — Doors B/C (split: guard-independent first)
Internal order follows the phase-2 doc's TDD steps, deliberately:
@ -68,6 +100,31 @@ 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
port.
## OKF v0.2 alignment — decisions between 3 and 4, code possibly later
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`.
**Why the decisions must land before Phase 4 code, even if the code does not.**
Phase 4 ports a *frozen, explicit* contract and uses the shared fixture suite as
the cross-runtime conformance oracle. Freezing that contract while its relation
to upstream v0.2 is undecided means freezing a v0.1 shape into two runtimes
instead of one, and the fixture suite would then certify the drift rather than
catch it. The expensive version of this mistake is discovered after the Node half
exists; the cheap version is a decision recorded now.
**Why it is not a phase.** v0.2's breaking surface is two supersessions, both
with documented consumer fallbacks, and every new field is optional. The only
measured shape problem is `DEFAULT`'s `generated` marker, which lives in commons'
ingest-spec §5 layer and is therefore not this repo's to change. So the work is
mostly *deciding and routing*, expressed through the Phase 3 profile object —
not a new implementation stage.
**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),
then the one blocking dependency — commons' answer on `generated` — and only then
any migration, as its own release.
## Phase 4 — Node half (two clocks)
**Coordination clock — start early, parallel to Stages 03, but only the
@ -117,6 +174,8 @@ These are the objective checks that a stage is done and the next may begin:
second-brain spec).
4. **Phase 4 code start:** each shape-independent coordination step has a
recorded sign-off; the contract-shape agreement is signed off *after* the
Phase 3 split-table, not before.
Phase 3 split-table, not before; and the OKF v0.2 open questions V1V5 are
each answered or explicitly deferred *in writing*, so the frozen contract
states its relation to upstream on purpose rather than by omission.
5. **Throughout:** `mypy --strict src/`, `ruff check .`, `ruff format --check .`
clean; boundary grep-gate empty.

View file

@ -0,0 +1,212 @@
# 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.

View file

@ -104,6 +104,12 @@ columns and are decomposed there rather than forced into one.
to block it (checked against the catalog's spec during the split-table step).
- Any change to commons' ingest-spec; if profile work reveals spec friction, it
is raised in commons, not patched locally.
- Aligning the contract with upstream OKF v0.2. This phase targets v0.1, and the
profile object is what makes the upstream version a *policy* rather than a
constant: no profile may hard-code an `okf_version` value, and the
frontmatter-set fields must be able to express either the legacy `timestamp`
or v0.2's `generated`. The decisions themselves live in
`docs/plan/okf-v0.2-alignment.md` and are sequenced after this phase.
## Verification