Caught while verifying facts for a consumer notice: the runbook stated in the past tense that `profile` landed as a keyword-only argument with a DEFAULT default. Measured -- no door takes the argument at all. The decision is real and stays; the claim that it is in the code was about to be repeated to a consumer as a property they could build on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
179 lines
9.5 KiB
Markdown
179 lines
9.5 KiB
Markdown
# Upstream OKF upgrade — runbook
|
|
|
|
Standing policy (operator, 2026-07-26): **this library always supports the current
|
|
latest version of Google OKF.** This document is the procedure that policy runs on.
|
|
It exists because a standing policy without a procedure is an intention, and because
|
|
the v0.1 → v0.2 round produced five findings that a spec reading alone had settled
|
|
wrongly — each step below names the concrete failure it prevents.
|
|
|
|
Written after the v0.2 round. Every prevented-failure note is something that
|
|
actually happened, not a hypothetical.
|
|
|
|
## The goal that shapes the procedure: this repo is a black box
|
|
|
|
An upstream release is our problem, not our consumers'. The target cost to a repo
|
|
that consumes OKF through this library is **a re-run, and nothing else.**
|
|
|
|
That is a design commitment with teeth, not an aspiration:
|
|
|
|
- **Support is additive — a new profile, never a migration.** Existing profiles stay
|
|
byte-stable. A consumer who does not opt in sees no change at all.
|
|
- **New public parameters are keyword-only with defaults.** A consumer's existing
|
|
positional call sites stay source-compatible across an upgrade. This is why
|
|
`profile` is specified as `*, profile: BundleProfile = DEFAULT` — **decided, not
|
|
yet shipped**: measured 2026-07-26, no door takes the argument at all. It turns
|
|
additivity
|
|
from something a consumer has to measure into a property of the signature.
|
|
- **Consumer golden fixtures must not churn.** If an upgrade rewrites bytes in a
|
|
consumer's frozen fixtures, the upgrade is wrong, not the fixture.
|
|
|
|
**The honest boundary — state it every time, never let the black box be oversold.**
|
|
The library absorbs *shape* changes. It cannot absorb upstream changes to content the
|
|
consumer authored. v0.2 superseded `timestamp` with `generated.at` and the body
|
|
`# Citations` list with `sources`; no library change makes a consumer's existing
|
|
`timestamp` field become something else. For that class the deliverable is not
|
|
absorption but a **measured exposure report, per consumer, before they ask** — which
|
|
is what step 5 produces.
|
|
|
|
## Trigger
|
|
|
|
"Always latest" decays silently: nothing fails when upstream ships and we do not
|
|
notice. So the re-check is an item on the release checklist — run it at every
|
|
release of this library, and record the result **even when unchanged**, because an
|
|
unrecorded check is indistinguishable from a skipped one.
|
|
|
|
Check `GoogleCloudPlatform/knowledge-catalog`, path `okf/`. A version bump appears
|
|
as a commit against `okf/SPEC.md` §12 and, in the v0.2 round, as an explicit
|
|
migration commit (`okf: migrate format and tooling to Open Knowledge Format v0.2`).
|
|
|
|
## Step 1 — Pin before reading anything
|
|
|
|
Record the exact commit SHA and read everything at it.
|
|
|
|
*Prevents:* reading a moving branch and treating the result as fact. In the v0.2
|
|
round both this repo and `portfolio-optimiser-commons` independently read `main`,
|
|
which felt like two confirmations and was one unstable reference read twice. Worse,
|
|
`SPEC.md` was edited **after** the v0.2 migration commit — so "the v0.2 commit" and
|
|
"the current spec text" were different objects, and either alone would have been a
|
|
partial answer.
|
|
|
|
## Step 2 — Enumerate the whole `okf/` tree, not just `SPEC.md`
|
|
|
|
List every file and directory at the pinned commit before deciding what to read.
|
|
|
|
*Prevents:* assuming the directory you know about is the one that matters. In the
|
|
v0.2 round `okf/` held `SPEC.md`, `README.md`, `pyproject.toml`, `src/`, `tests/`,
|
|
`samples/` **and** `bundles/`. The last one held the actual v0.2 example bundles and
|
|
was not on anyone's list until the tree was enumerated.
|
|
|
|
## Step 3 — Read the shipped examples, not only the normative text
|
|
|
|
This is the step the v0.2 round skipped, and it is the reason this document exists.
|
|
|
|
**A spec says what is permitted. An example shows what upstream actually emits.**
|
|
Those differ, and when we are about to freeze a fixture and hand it to consumers,
|
|
the second one is what we are being measured against.
|
|
|
|
Two traps, both hit in the v0.2 round:
|
|
|
|
- **A directory called `samples/` was not samples of the format.**
|
|
`okf/samples/ga4_merch_store/` is a runner config (`README.md` + `seeds.txt`) that
|
|
drives the reference agent to *generate* a bundle. It says nothing about output
|
|
shape. The real examples were in `okf/bundles/` — four of them.
|
|
- **Fetch tooling that converts to markdown will strip or reformat frontmatter.**
|
|
Byte-level questions need byte-level fetches. Check for a BOM, check the trailing
|
|
newline, check whether a value is quoted. Those three are exactly the properties
|
|
that break downstream gates and exactly the ones a summarizing fetch destroys.
|
|
|
|
What reading the four v0.2 bundles produced, none of it derivable from `SPEC.md`:
|
|
|
|
| Finding | Consequence |
|
|
|---|---|
|
|
| No reference bundle declares `okf_version` at all; root `index.md` has no frontmatter | §12 is a MAY and upstream declines it. Our stricter posture became a *decision* instead of an assumption |
|
|
| `generated` is written for **human-authored** content (`by: human:…`) | It can never be an ownership or machine-generated predicate. Governs the collision gate |
|
|
| Real frontmatter is multi-line block YAML: block lists of multi-key mappings, nested mappings, flow sequences, booleans, dates | Re-sized the emitter work. It had been scoped against a list of strings |
|
|
| §7's canonical tool actor is `<producer>/<version>`, and upstream uses it | Counter-evidence to a recommendation we had already sent to another repo |
|
|
|
|
## Step 4 — Produce the diff, classified
|
|
|
|
From the spec's own "Changes from vN" section **plus** the example evidence,
|
|
classify every change as:
|
|
|
|
- **Breaking** — name it, and measure consumer exposure in step 5.
|
|
- **Additive** — new optional fields, new conventional headings.
|
|
- **Deferred upstream** — a format specified but its runtime protocol left to a
|
|
future revision. The format is supportable; an unspecified runtime is not
|
|
something to build against. Record it as out of scope *on upstream's deferral*,
|
|
not on our preference.
|
|
- **Observed but undocumented** — present in an official bundle, not found in the
|
|
spec sections read. Record as unresolved. Do not silently promote it to either
|
|
category. (v0.2 round: a `not:` family in `metrics/gross-margin.md`.)
|
|
|
|
## Step 5 — Measure our exposure and each consumer's, in that order
|
|
|
|
Ours: grep the profiles, the fixtures, and the emitter for every field the diff
|
|
touches. Cite file:line. Never reason about a key set — measure it.
|
|
|
|
Theirs: for each breaking change, state what would have to be true for it to reach
|
|
them, then **ask them to measure it against their real data** rather than telling
|
|
them whether they are affected. In the v0.2 round both `portfolio-optimiser-claude`
|
|
and `linkedin-studio` came back with measured zero exposure and found the framing
|
|
useful precisely because it was falsifiable.
|
|
|
|
## Step 6 — Plan additively, then pilot before general availability
|
|
|
|
The plan is a new profile, sequenced deliverables, and stated invariants. Rollout is
|
|
pilot-first: a pre-release tag to a small pilot set chosen for signal, revised on
|
|
their feedback, then GA. Flipping the `OKF_LATEST` alias **is** the GA event, not a
|
|
merge side effect.
|
|
|
|
State each pilot test's expected results **before** the run, numbered, plus what
|
|
would surprise us. An expectation we get wrong is a better result than a clean run;
|
|
only a stated expectation can be falsified by someone else's run.
|
|
|
|
**Name fixtures by repo and path, never by path alone.** In the v0.2 round both this
|
|
repo and `portfolio-optimiser-claude` had a directory named
|
|
`examples/ingest-golden-file/` holding *different* fixtures with zero content
|
|
overlap. A pilot baseline was agreed by name, and the name pointed at two files.
|
|
|
|
## Step 7 — Inform every OKF-consuming repo
|
|
|
|
All of them, not only the pilots, and not only when something breaks.
|
|
|
|
Send a **bounded loop of directed messages, not a broadcast** — a broadcast is
|
|
inherited by every future repo, which is the wrong lifetime for a
|
|
version-specific notice.
|
|
|
|
Each message carries:
|
|
|
|
1. The version, the pinned commit, and where to read it.
|
|
2. The breaking changes by name, with the shape of the exposure — so they can
|
|
measure themselves rather than take our word.
|
|
3. **The black-box promise and its boundary**: their cost is a re-run; existing
|
|
profiles are byte-stable; new parameters are keyword-only. And the part we
|
|
cannot absorb, said plainly.
|
|
4. What we want back, if anything, and what happens if they say nothing.
|
|
|
|
Consumers to cover (2026-07): `portfolio-optimiser-commons` (spec authorship),
|
|
`portfolio-optimiser`, `portfolio-optimiser-claude`, `claude-code-llm-wiki`,
|
|
`catalog`, `okr`, `linkedin-studio`, `ms-ai-architect`. Re-derive this list each
|
|
round rather than trusting it — it is a premise like any other.
|
|
|
|
## Step 8 — Close the loop in the repo
|
|
|
|
The plan doc records the pinned commit, the classified diff, the measured exposure,
|
|
and every correction a consumer sent back. Corrections stay visible: a premise that
|
|
survived verification is only known to have survived if the check is recorded, and a
|
|
claim we withdrew is only safely withdrawn if the withdrawal is written where the
|
|
claim was.
|
|
|
|
## Invariants this procedure protects
|
|
|
|
- No profile hard-codes an upstream version.
|
|
- No bundle declares a version its shape has not earned. The spec may permit
|
|
declaring conformance without a checkpoint; we decline. *Conform first, claim
|
|
after.*
|
|
- `DEFAULT` states commons' ingest-spec §5 layer — raised there, never patched here.
|
|
- `STRICT_V1` mirrors a consumer's ratified contract — never changed from here.
|
|
- The value of `okf_version` belongs to the catalog.
|
|
- Security stays the guard's domain in every version.
|