docs: every upstream OKF release runs a runbook, and this repo is a black box
The standing always-latest policy had no procedure, and v0.1 -> v0.2 showed what that costs: the spec was read, the shipped example bundles were not, and five findings a spec reading had settled wrongly surfaced only after someone asked whether we had studied the examples. One of them re-sized the release itself. docs/upstream-okf-upgrade-runbook.md is that procedure. Every step names the concrete failure it prevents, and each is a failure that actually happened: - Pin before reading. Two repos read main independently, which felt like two confirmations and was one unstable reference read twice -- and SPEC.md was edited after the migration commit, so neither alone was the whole answer. - Enumerate the whole okf/ tree. bundles/ held the real examples and was on nobody's list until the tree was listed. - Read the shipped examples, not only the normative text. A directory named samples/ turned out to be a runner config, not samples of the format. And markdown-converting fetch tooling strips exactly the byte-level properties (BOM, trailing newline, quoting) that break downstream gates. - Name fixtures by repo AND path. Two repos had examples/ingest-golden-file/ holding different fixtures with zero content overlap, and a pilot baseline was agreed by a name that pointed at two files. The black-box commitment is now an invariant in CLAUDE.md rather than an emergent property: a consumer's cost for an upstream release is a re-run. Additive profiles, byte-stable existing profiles, keyword-only new parameters so positional call sites survive, no churn in consumer fixtures. The boundary is stated rather than glossed -- shape changes are absorbed, upstream changes to consumer-authored content are not, and those get a measured exposure report per consumer instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
This commit is contained in:
parent
4669f32b47
commit
499e96ed92
2 changed files with 194 additions and 0 deletions
17
CLAUDE.md
17
CLAUDE.md
|
|
@ -93,6 +93,23 @@ 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.
|
||||
|
||||
**Every upstream release runs `docs/upstream-okf-upgrade-runbook.md`.** Pin the
|
||||
commit, enumerate the whole `okf/` tree, **read the shipped example bundles and not
|
||||
only `SPEC.md`**, classify the diff, measure our exposure and each consumer's, plan
|
||||
additively, pilot before GA, then inform every OKF-consuming repo. The runbook is
|
||||
not optional and not a summary of good intentions: each step names the concrete
|
||||
failure it prevents, and all of them are failures that happened during v0.1 → v0.2.
|
||||
|
||||
**This repo is a black box for its consumers.** The target cost of an upstream
|
||||
release to a consuming repo is **a re-run, nothing more**: support is additive
|
||||
(a new profile, never a migration), existing profiles stay byte-stable, new public
|
||||
parameters are keyword-only with defaults so positional call sites stay
|
||||
source-compatible, and consumer golden fixtures must not churn. The boundary is
|
||||
stated every time rather than glossed — the library absorbs *shape* changes, not
|
||||
upstream changes to content a consumer authored (v0.2's `timestamp` and
|
||||
`# Citations` supersessions). For that class the deliverable is a measured exposure
|
||||
report per consumer, sent before they ask.
|
||||
|
||||
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
|
||||
|
|
|
|||
177
docs/upstream-okf-upgrade-runbook.md
Normal file
177
docs/upstream-okf-upgrade-runbook.md
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# 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` landed as `*, profile: BundleProfile = DEFAULT`: 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue