The plan's own line said steps 1-3 change no behavior. V-A7's construction gate made that untrue, and a stale premise in a plan is the class of thing this repo has already paid for twice. Recorded where it was wrong, with the operator decision that put the gate before D2 rather than at it, and with Door C's six tolerance cases measured rather than expected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A2aKJxLejT9S8jYwoZ9fut
1025 lines
61 KiB
Markdown
1025 lines
61 KiB
Markdown
# OKF v0.2 support — plan
|
||
|
||
Status: **committed implementation track.** Standing policy (operator,
|
||
2026-07-26): *the library always supports the current latest version of Google
|
||
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` (sequencing),
|
||
`docs/plan/phase-3-configurable-contract.md` (the profile object that carries
|
||
this), `docs/phase-3-split-table.md` (the profile/guard/consumer column
|
||
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 — V-A1 is DISCHARGED
|
||
|
||
**Pinned 2026-07-26 to `3fcbb9f828c2f23d109c855ee403c3a4c81f3a96`** in
|
||
`GoogleCloudPlatform/knowledge-catalog` (2026-07-24, "Update SPEC.md"). v0.2
|
||
itself landed one commit earlier in `780fe9d30b5bbca8931256edf1d0290d6bda5462`
|
||
(2026-07-24, "okf: migrate format and tooling to Open Knowledge Format v0.2
|
||
(#227)"). Note that `SPEC.md` was edited **after** the migration commit, so "the
|
||
v0.2 commit" and "the current spec text" are not the same object — which is
|
||
precisely why the pin exists.
|
||
|
||
The earlier reading was taken from `main` on 2026-07-26 — a branch, not a tag —
|
||
and commons independently confirmed their own check hit the same moving
|
||
reference. Two readings of one unstable reference are not two confirmations.
|
||
That is now closed: everything below is read at the pinned commit.
|
||
|
||
Minor correction while pinning: `CLAUDE.md` says v0.2 shipped 2026-07-25. The
|
||
commit dates say 2026-07-24. Not load-bearing, but it was asserted rather than
|
||
measured.
|
||
|
||
## Grounding against upstream's own reference bundles (2026-07-26)
|
||
|
||
The spec was read in session 19. **The shipped examples were not**, and reading
|
||
them changed several things a spec reading alone had settled wrongly. A normative
|
||
spec says what is *permitted*; the reference bundles show what upstream actually
|
||
*emits* — and we were about to freeze a fixture and hand it to three pilots.
|
||
|
||
Read at the pinned commit: `okf/bundles/{acme_retail,crypto_bitcoin,ga4,stackoverflow}/`
|
||
— four bundles migrated to v0.2 by the tooling itself. `okf/samples/ga4_merch_store/`
|
||
is **not** a bundle: it is a runner config (`README.md` + `seeds.txt`) that drives
|
||
the reference agent to *generate* one, so it says nothing about output shape.
|
||
|
||
### U1 — No reference bundle declares `okf_version` at all
|
||
|
||
Measured on all four root `index.md` files: no frontmatter block whatsoever, no
|
||
BOM, LF-terminated, body starts directly at `# Subdirectories`. §12 makes the
|
||
declaration a **MAY**, and upstream's own tooling declines it.
|
||
|
||
Three consequences, all live:
|
||
|
||
- **A-E6 asks for a stricter posture than the reference implementation.** That is
|
||
a defensible choice — a bundle that declares its target version is more useful
|
||
to a gate than one that does not — but it must be a *decision*, not an
|
||
assumption that we are following upstream. It is now the former.
|
||
- **po-claude's "deviation" #2 was conformance, not deviation.** Their root
|
||
`index.md` has no frontmatter — the same shape as all four reference bundles.
|
||
Our expectation text was the outlier.
|
||
- **catalog's gate reports `MISSING` on every official Google reference bundle.**
|
||
Their gate reads `^okf_version:` from the root index. Worth telling them: the
|
||
BOM hole they measured is one way to get a meaningless green, and a
|
||
spec-conformant bundle that simply exercises the MAY is another.
|
||
|
||
### U2 — Root `index.md` is the ONLY index permitted to carry frontmatter
|
||
|
||
§12, verbatim: `okf_version` goes "in a bundle-root `index.md` frontmatter block
|
||
(**the only place frontmatter is permitted in an `index.md`**)". Confirmed in the
|
||
bundles: `tables/index.md` has no frontmatter. This constrains D5's fixture and
|
||
the index policy directly — a nested index that grows a frontmatter block is
|
||
non-conformant, not merely unusual.
|
||
|
||
### U3 — `generated` is attribution, and it is used for HUMAN-authored content
|
||
|
||
`metrics/gross-margin-legacy.md` carries
|
||
`generated: { by: human:jsmith@acme, at: 2024-01-15T10:00:00Z }`.
|
||
|
||
This is the empirical confirmation of commons' axis finding, and it is stronger
|
||
than the argument they made from the spec text alone: `generated` **cannot** serve
|
||
as a machine-generated predicate, because upstream writes it for hand-authored
|
||
files. Any implementation deriving "is this generated?" or "do we own this file?"
|
||
from the presence of `generated` is wrong under v0.2. `_is_ingest_owned` must key
|
||
on `generated.by == <our actor>` **and** `ingest_manifest`, exactly as commons
|
||
specified — presence alone is meaningless.
|
||
|
||
### U4 — Real v0.2 frontmatter is multi-line block YAML, not `key: value` lines
|
||
|
||
This is the finding with the largest implementation cost, and it invalidates the
|
||
sizing of tag requirement 1. Observed verbatim in `computations/gross-margin-period.md`:
|
||
|
||
```yaml
|
||
tags: [finance, margin, attested]
|
||
parameters:
|
||
- { name: period_start, type: date, required: true }
|
||
executor:
|
||
resource: skills/run-on-bq.md
|
||
receipt: [job_id, executed_sql, result]
|
||
generated: { by: reference_agent/gemini-2.5-pro, at: 2026-06-30T14:00:00Z }
|
||
verified:
|
||
- { by: human:jsmith@acme, at: 2026-07-01T09:00:00Z }
|
||
sources:
|
||
- id: margin-standard
|
||
resource: policies/margin-standard.md
|
||
title: Cost Allocation & Margin Standard (FY2026)
|
||
author: human:jsmith@acme
|
||
last_modified: 2026-06-15
|
||
```
|
||
|
||
Present in one file: flow sequences (`tags`, `receipt`), block lists of flow
|
||
mappings (`parameters`, `verified`), nested block mappings (`executor`), block
|
||
lists of **multi-key** mappings (`sources`), booleans (`required: true`), and
|
||
bare dates. §11 requires "a parseable **YAML** frontmatter block".
|
||
|
||
So requirement 1 — "`emit` accepts a block-list value (`sources`)" — was scoped
|
||
against a list of *strings*. The canonical shape is a list of *mappings with five
|
||
keys*. The plan's own rule applies to itself here: measure, do not reason.
|
||
|
||
**Re-sized 2026-07-26**, and the answer runs the other way. What upstream
|
||
*emits* does not settle what we *owe*: the re-sizing is recorded under
|
||
requirement 1 below, and the emitter does not change at all, because the form we
|
||
owe A-E4 is not upstream's five-key block list.
|
||
|
||
It also sharpens the §5 tension for commons: their ingest-spec requires "All
|
||
values MUST be single-line" (`:158`). `generated` as an inline flow mapping
|
||
satisfies that, which is what commons measured. Upstream's canonical `sources`
|
||
does **not** — it is inherently multi-line. Expressible as inline flow if forced,
|
||
but that is a deviation from the reference shape, and it is a `DEFAULT`-profile
|
||
question rather than an `OKF_V0_2` one.
|
||
|
||
### U5 — §7 actor convention is counter-evidence to our own V1 recommendation
|
||
|
||
Verbatim, three forms: `<producer>/<version>` for agents and tools (example given:
|
||
`reference_agent/gemini-2.5-pro`), `human:<id>` for a person, `process:<id>` for
|
||
an automated process. Consumers keying trust off the `human:` prefix means
|
||
producers **MUST** use it for hand-authored or human-confirmed content.
|
||
|
||
We recommended (d) `process:<fixed id>` to commons earlier today, partly on the
|
||
argument that a producer-and-version string inside a byte-compared shared golden
|
||
breaks `:29`. That argument stands. But **upstream's own reference implementation
|
||
uses the `<producer>/<version>` form**, which is real evidence for (b) that we did
|
||
not have when we wrote. Sent to commons as a correction rather than left to
|
||
surface during ratification — the option set they hand the operator has to carry
|
||
both facts.
|
||
|
||
### U6 — Conformance and consumer tolerance, verbatim
|
||
|
||
A bundle is conformant if every non-reserved `.md` has a parseable YAML
|
||
frontmatter block, every block has a non-empty `type`, and reserved filenames
|
||
(`index.md`, `log.md`) follow §8/§9. §4: `type` is "the only always-required key;
|
||
a concept carrying just `type` is fully conformant".
|
||
|
||
Consumers **MUST** treat a bare `verified` mapping as a one-element list (§5.2) —
|
||
a hard requirement on D1's reader, not a nicety. Consumers **MUST NOT** reject on:
|
||
missing optional frontmatter, unknown `type` values, unknown additional keys,
|
||
broken cross-links, missing `index.md`. `status` absent defaults to `stable`.
|
||
|
||
### U7 — One unresolved observation
|
||
|
||
`metrics/gross-margin.md` carries a `not:` family (`- term / why / instead`) that
|
||
we could not locate in the spec sections read. It is either a v0.1 field we never
|
||
enumerated or an undocumented one. **Not resolved**, recorded so it is not
|
||
mistaken for something already checked. It touches nothing in the tag.
|
||
|
||
### What this changes
|
||
|
||
Tag requirement 1 was re-sized (U4 → requirement 1): the emitter turns out not to
|
||
change at all, and A-E4 is corrected to the flow form to match. Requirement 5's
|
||
fixture must respect U2
|
||
and take U1 as an explicit decision. `_is_ingest_owned` gets U3 as its governing
|
||
fact. D1's reader gets the §5.2 bare-mapping MUST from U6. V1's option set gets
|
||
U5. None of it blocks steps 1–3, which change no behavior.
|
||
|
||
## What v0.2 actually changes
|
||
|
||
Two supersessions — the only backward-breaking changes, both with 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 upstream (§5) — and all in scope for us under the standing
|
||
policy: `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`.
|
||
- **`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 sizes the parser work: the canonical form for `generated`
|
||
and a single `verified` is an **inline flow mapping** on one line
|
||
(`generated: { by: …, at: … }`), which the existing scalar parser already
|
||
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
|
||
|
||
| 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]`) | 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 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 |
|
||
|
||
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
|
||
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
|
||
surface this repo may not unilaterally change. Under the additive design this
|
||
stops being on our critical path (D2, V1).
|
||
|
||
The interaction that keeps (1) true and must never be violated: the `timestamp`
|
||
fallback is granted only when `generated` is **absent**. A profile emitting 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`, `STRICT_V1` has no `generated` — and no profile may be
|
||
allowed to reach that combination. This becomes a profile-construction invariant
|
||
(V-A7).
|
||
|
||
## The sequencing rule we impose on ourselves
|
||
|
||
**Conform first, claim after.** No bundle declares an `okf_version` its shape has
|
||
not earned.
|
||
|
||
The spec does not require this — §12 is a MAY with no conformance checkpoint, 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. Concretely, the
|
||
v0.2 profile declares `"0.2"` only once D1–D3 and their fixtures are green.
|
||
|
||
## Deliverables
|
||
|
||
**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:
|
||
|
||
- (a) Widen the value type in place (`str | Mapping | list`). Cheapest to write,
|
||
worst to consume: every existing caller must newly narrow, and `mypy --strict`
|
||
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.
|
||
|
||
**D2 — An `OKF_V0_2` profile.** Frontmatter schema covering the §5 families;
|
||
`generated: { by: "llm-ingestion-okf/<version>", at: <ingested_at> }` using the
|
||
§7 actor convention; `sources` emission from the manifest's source; `status` and
|
||
`stale_after` expressible; root-index `okf_version: 0.2`.
|
||
|
||
Also an `OKF_LATEST` alias, which is how the standing policy becomes visible in
|
||
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.
|
||
|
||
`OKF_LATEST` means *the latest version supported as stable*, not the latest
|
||
version present in the tree. During the pilot it therefore keeps pointing at the
|
||
v0.1 semantics, and **flipping it to v0.2 is the GA event** — a single, auditable
|
||
action rather than a side effect of a merge. This is what keeps the alias from
|
||
becoming the footgun that drags tracking consumers into a provisional surface.
|
||
|
||
`at` binds to the already-validated required `ingested_at` argument, so no
|
||
wall-clock default is introduced and determinism holds.
|
||
|
||
**D3 — Door C accepts any v0.2 bundle.** This is where "we support v0.2" is most
|
||
user-visible: the §14 consumer tolerance MUST NOTs, plus the bare-`verified`
|
||
coercion. Measured with one test per rule — Door C is a consumer in the OKF
|
||
sense and either honours these or does not.
|
||
|
||
**D4 — `Attested Computation`: format yes, runtime no.** Support the type and its
|
||
fields (`runtime`, `parameters`, `computation`, `executor`, `attester`) for
|
||
parse, validate, round-trip, and index. Do **not** implement execution or
|
||
attestation: the receipt and verdict wire formats are explicitly deferred
|
||
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.
|
||
|
||
**D5 — v0.2 golden fixtures.** A byte-exact v0.2 bundle in `examples/`. This is
|
||
what converts "we support v0.2" from a claim into a test, and in phase 4 it
|
||
becomes the cross-runtime conformance oracle for the Node half too.
|
||
|
||
**D6 — Keep the policy from decaying.** "Always latest" silently rots unless
|
||
something checks. Two mechanisms, both cheap: the spec commit is pinned and
|
||
recorded in this doc (V-A1), and the release checklist gains one item — re-check
|
||
upstream for a newer OKF version and record the result, even when unchanged. A
|
||
standing policy with no detection mechanism is an intention, not a policy.
|
||
|
||
## Rollout — pilot before general availability
|
||
|
||
Operator directive 2026-07-26: consumers are better served by getting the latest
|
||
version early, testing it, and reporting back than by us holding it until we
|
||
judge it finished. So v0.2 ships to a small pilot set first, and general
|
||
availability is what happens *after* the findings are addressed.
|
||
|
||
This closes a gap the plan otherwise had. Every test in the suite — including
|
||
V-A8 against upstream's reference implementation — asks whether the output is
|
||
*conformant*. None asks whether it is *usable*: whether a real bundle is awkward
|
||
to construct, whether a rejection message tells the consumer anything actionable,
|
||
whether the profile can express what their actual data needs. Only a consumer
|
||
running real data surfaces that.
|
||
|
||
### The pilot set, chosen for signal rather than convenience
|
||
|
||
Three repos, each exercising a different axis, and deliberately cheap for them:
|
||
|
||
| Repo | Axis under test | What we ask of them | Their cost |
|
||
|---|---|---|---|
|
||
| `portfolio-optimiser-claude` | **Producer path.** Real Door A ingestion emitting v0.2 from a real manifest | Pin the pre-release tag, run one real manifest, report | one run |
|
||
| `catalog` | **Gate acceptance.** Does a bundle *declaring* `okf_version: 0.2` actually pass their gate — measured against their gate, not our reading of its regex | Run their existing gate on our fixture. No code change | one gate run |
|
||
| `claude-code-llm-wiki` | **Expressiveness.** Can the profile object express a strict consumer at v0.2, over 524 real documents | Nothing. We read their bundle and send them the report | zero |
|
||
|
||
The wiki entry is the important design detail: **most of the pilot is read-only
|
||
on our side.** We run the v0.2 reader over real consumer bundles and report what
|
||
we find. That needs no adoption, no writes into their tree (O2 holds), and no
|
||
change to a contract their operator ratified. Only the *producer* axis requires a
|
||
consumer to do anything.
|
||
|
||
Deliberately excluded, with reasons: `okr` (Node side, not yet lifted — wrong
|
||
time); `linkedin-studio` (their `ingest/published/` provenance grammar is
|
||
deliberately not normalized, and v0.2's provenance families would put implicit
|
||
pressure on exactly that carve-out); `portfolio-optimiser-commons` (they are
|
||
deciding V1 — a participant, not a test site); `llm-ingestion-pipeline-security`
|
||
(not an OKF consumer in this sense).
|
||
|
||
### Stages
|
||
|
||
- **P1 — read-only sweep (us).** Run the v0.2 reader over real bundles from the
|
||
pilot set. Finds parser gaps, tolerance-rule violations, and expressiveness
|
||
gaps at zero risk to anyone. Runs as soon as D1–D3 are green.
|
||
- **P2 — one producer, opt-in.** `portfolio-optimiser-claude` emits a v0.2 bundle
|
||
against the pre-release tag.
|
||
- **P3 — gate check.** `catalog` runs their real gate on P2's output.
|
||
- **P4 — fix, then GA.** Address findings, then flip `OKF_LATEST` as its own
|
||
release.
|
||
|
||
### Pilot responses — all three accepted, and the asking paid for itself
|
||
|
||
All three replied on 2026-07-26, before the tag exists. That timing is the whole
|
||
return on asking early: every finding below changes the tag's *design* rather
|
||
than showing up as a failed run afterwards.
|
||
|
||
- **`portfolio-optimiser-claude` — yes, conditional.** Their conditions are their
|
||
own hygiene, not reservations about us: scratch venv, no edit to their
|
||
`ingest.py`, no bump of their production pin (`v0.3.2` = `f14c075`). Their
|
||
exposure to both backward-breaking changes is **measured at zero** against
|
||
their byte-frozen golden: they have no `timestamp` field at all (they have
|
||
`ingested_at`, a different field with a different mandate), and no `# Citations`
|
||
heading in any body they produce or read. Their current form is literally
|
||
`generated: true`, so A-E3 is calibrated correctly against them.
|
||
|
||
**Baseline: `examples/ingest-golden-file/` — and the name is ambiguous.**
|
||
Measured 2026-07-26 after commons checked the claim: *both* repos have a path by
|
||
that name holding *different* fixtures. Ours is `ingest-orders.md` +
|
||
`ingest-products.md`; theirs is `ingest-costs.md` + `ingest-edge.md`; the
|
||
manifests differ (`cmp`: line 3, char 41); content-file overlap is **zero**. The
|
||
only shared filename is `index.md`, and those are byte-different.
|
||
|
||
So the reason we gave for choosing it — "the shared fixture against the sibling
|
||
implementation, ratified by two independent implementations" — is **false**. We
|
||
took it from their message and passed it on the same day without measuring,
|
||
having a standing rule that an incoming message is a premise rather than fact
|
||
and applying it only to spec readings. Commons caught it.
|
||
|
||
The fixture remains the right choice, for a different reason: A-E1 asserts
|
||
byte-identity with what *their pinned version* produces, which is a claim about
|
||
*their* baseline, not ours. The pilot runs **their** fixture, and the report
|
||
states repo and path explicitly. Corrected to them before the run — A-E1 is the
|
||
stop condition, so a mismatched baseline would have surfaced as a profile defect.
|
||
- **`catalog` — yes.** Accepted the provisional terms explicitly. Runs after
|
||
2026-07-30. Pre-measured their own gate on a synthetic proxy (6 runs,
|
||
`okf-check.mjs` unmodified at `6a72b26`) because we asked them not to trust a
|
||
*reading* of their regex — so they declined to trust one either. They are
|
||
fixing neither the quoting hole nor the BOM hole before the run, deliberately:
|
||
we asked for their existing unmodified gate, and hardening it now would make
|
||
Test B measure something else.
|
||
- **`claude-code-llm-wiki` — yes to both the read and the report**, with one
|
||
condition on our public track (below). They pre-accept none of C-E1..C-E4 and
|
||
will answer per expectation. One qualifier on what they can answer: "load-
|
||
bearing" for them means load-bearing for `validate.py`'s gates **and** for
|
||
consumers they do not control — they will say which of the two any answer rests
|
||
on.
|
||
|
||
**A premise of ours they corrected.** The pilot request said "no code change on
|
||
your side beyond the pin". That is false for po-claude's call path: their adapter
|
||
calls `materialize_bundle` with three positional arguments and exposes no
|
||
`profile` parameter, so the procedure's `profile=DEFAULT` / `profile=OKF_V0_2`
|
||
has nowhere to land. They solve it in scratch without touching their repo, so
|
||
their condition holds — but it means **A-E1 measures the library API, not their
|
||
adapter seam**, and we must read the result as such. Requirement 6 above is the
|
||
design response.
|
||
|
||
**A defect they closed rather than had us design around.** po-claude's index
|
||
parser died with `ValueError` on any `index.md` carrying frontmatter without a
|
||
`type` key — which is exactly what A-E6 produces, and it would have killed all of
|
||
`navigate_bundle`, not just the index read. Fixed on their side in `f41264f`
|
||
before we designed anything, mutation-proven both directions. **Do not design
|
||
around it.** The generalizable part is the assumption underneath it — "a
|
||
generated index has no frontmatter" — which was frozen into their golden where
|
||
nothing could catch it, and is not peculiar to them. Any consumer stamping
|
||
`okf_version` into a generated index may hold it.
|
||
|
||
**Their frontmatter parser is line-oriented, not YAML** (`line.partition(":")`),
|
||
which is the same shape as ours and gives v0.2's new families a predictable
|
||
failure mode elsewhere: `sources` as a block list pollutes the dict with false
|
||
keys (`- resource`, `author`), and `generated` as an inline flow mapping is
|
||
stored as its literal string. Latent and not reached today: last key wins, so a
|
||
nested `type:` anywhere in a block list would overwrite a file's own `type`. None
|
||
of the fields we specified is named `type`, so this is a warning about the
|
||
*form*, not about v0.2 — and it is one more reason the structured reader (D1)
|
||
is real work rather than a formality.
|
||
|
||
**Dropping `timestamp` is not free at the gate.** Catalog does not require it —
|
||
it sits in RECOMMENDED, so absence is a WARNING with no exit effect. But a v0.2
|
||
bundle that drops the superseded field draws one warning **per concept file**,
|
||
noise that grows with bundle size. Whether their gate should stay silent about a
|
||
superseded field is a change on their side, to be decided after the pilot, not
|
||
before.
|
||
|
||
### The wiki's condition on our public track
|
||
|
||
Their bundle and code are **private** pending an Anthropic ToS assessment (their
|
||
E3, operator decision 2026-07-26). This repo pushes to `open/`. The condition,
|
||
which they name as the one thing that cannot be reversed:
|
||
|
||
- The Test C report goes to them **via coord**, never as a file in a public tree.
|
||
- In public files here, stay at **form level**. Key names, counts, gate names and
|
||
profile fields are fine and already present, and they have no objection to
|
||
those. **Not** page bodies, **not** full title or path lists from their
|
||
`bundle/`, **not** Anthropic-derived text (release notes, documentation prose).
|
||
|
||
This is not an objection to the pilot, and it costs the pilot nothing: Test C
|
||
measures whether a profile can *express* their contract, which is a question
|
||
about shapes and key sets. Nothing it needs to answer requires quoting their
|
||
content. The constraint is recorded in `CLAUDE.md` as well, because it binds
|
||
every future session writing into this repo, not just the ones running Test C.
|
||
|
||
### What `v0.5.0a1` minimally requires — much less than the deliverable list
|
||
|
||
Walking each pilot test back to what it actually exercises produces a result the
|
||
deliverable ordering had obscured: **none of the three tests needs D1's
|
||
structured reader.**
|
||
|
||
- Test A **emits** v0.2. Both `generated` and — after requirement 1's re-sizing —
|
||
`sources` are inline flow constructs, so both are formatted strings the
|
||
existing emitter renders verbatim. Neither requires reading one back.
|
||
- Test B runs catalog's gate on a fixture. That fixture is Test A's output.
|
||
- Test C validates the wiki's **v0.1-shaped** documents (scalar frontmatter,
|
||
`timestamp`, no `generated`) against a v0.2 *variant profile*. Their documents
|
||
contain no v0.2 families, so block-list reading is never reached.
|
||
|
||
The structured reader exists to **consume third-party v0.2 bundles** — that is
|
||
D3/Door C, and no pilot repo sends us one. So the tag needs:
|
||
|
||
1. **`sources` emits as an inline flow sequence, and the emitter does not
|
||
change.** Re-sized 2026-07-26 against the code rather than against its tests.
|
||
|
||
Measured: `emit` (`profiles.py:197`) takes `Mapping[str, str]` and renders one
|
||
`f"{key}: {value}"` line per key through `_render` (`:211`), verbatim but for
|
||
`source_query`'s whitespace collapse; `_is_legal_value` (`:97`) admits a
|
||
string or a non-empty list of **strings**; the only production call sites are
|
||
`materialize.py:166` and `inbox.py:129`, both passing `dict[str, str]`.
|
||
|
||
The earlier "discharged" note read the validator correctly and drew too much
|
||
from it — a list of strings is admitted, a list of *mappings* is not — so it
|
||
did not cover the shape U4 measured upstream. But the gap it left is not the
|
||
gap U4 predicted either. A-E4 owes "at least a `resource`", not upstream's
|
||
five keys, and a manifest source has no `author`, no `last_modified`, and no
|
||
bundle-internal `resource` in upstream's sense. "A field with no reader is not
|
||
written" forbids inventing them. The question was therefore never how large a
|
||
YAML emitter we need; it was which form `sources` takes.
|
||
|
||
Chosen: `sources: [{ id: <source id>, resource: <source reference> }]` — the
|
||
same inline flow form A-E3 already chose for `generated`. Three reasons beyond
|
||
not carrying two shapes for two fields in one frontmatter block:
|
||
|
||
- **It keeps `parse_frontmatter` line-oriented and correct.** A real block
|
||
list pollutes that parser with false keys (`- id`, `resource`) — the failure
|
||
mode documented for po-claude at `:444-448` — and `_is_ingest_owned`, the
|
||
one piece of this tag we called risky, reads *through* that parser. The
|
||
block form would force the collision gate and the parser to be hardened in
|
||
the same session; the flow form lets requirement 3 be done in isolation.
|
||
- **It satisfies commons' §5 "all values MUST be single-line"**, so the
|
||
`DEFAULT` seam U4 opened stays closed and the tag does not wait on commons.
|
||
- **§11 requires "a parseable YAML frontmatter block", not a block one.** A
|
||
flow sequence of flow mappings is parseable YAML. Conform first, claim after.
|
||
|
||
The cost is real and is stated rather than glossed: this deviates from the
|
||
shape upstream's own tooling emits (U4). Like U1, it is a **decision**, not
|
||
compliance we may claim — and it is cheap to reverse, since moving to the
|
||
block form later is a new profile, not a migration.
|
||
|
||
Consequence for the estimate: requirement 1 is *smaller* than both the
|
||
original sizing and U4's escalation of it — zero emitter work, the change is
|
||
in the materializer's value construction — and A-E4 is corrected to match.
|
||
2. An `OKF_V0_2` profile constant — same shape as the existing `STRICT_V1` work.
|
||
3. `_is_ingest_owned` becomes profile-aware, since the ownership marker now
|
||
differs per profile.
|
||
4. `sources` derived from the manifest source in the materializer.
|
||
5. One v0.2 golden fixture in `examples/`, generated by the code rather than
|
||
hand-authored, or it does not test the emitter. Its root `index.md` carries
|
||
`okf_version: 0.2` **unquoted and BOM-free** — both measured as gate-breaking
|
||
by catalog (see A-E6), so both get an assertion on the raw bytes rather than
|
||
on the parsed value, which would mask exactly these two defects.
|
||
6. **`materialize_bundle` takes `profile` as a keyword-only argument defaulting
|
||
to `DEFAULT`.** Added on po-claude's request, and it is nearly free: the
|
||
signature already has a `*` marker, so the parameter goes behind it and every
|
||
existing three-positional call site stays source-compatible. The reason to
|
||
take the request is that it converts the additivity A-E1 asserts from
|
||
something a consumer measures into a property of the signature itself.
|
||
7. The tag.
|
||
|
||
Plus TDD steps 1–3 first as the safety net; they change no behavior.
|
||
|
||
**Consequence: V2 is off the critical path.** V2 chooses the *reader's* design,
|
||
and the reader now lands after the pilot. The emitter's shape is not contentious
|
||
in the same way. The tag is not waiting on a decision.
|
||
|
||
**Risk concentrates in item 3**, not in volume: `_is_ingest_owned` is the
|
||
pre-mutation collision gate, so a defect there is expensive and quiet. Its
|
||
characterization test (TDD step 1) is written before it is touched. After the
|
||
re-sizing above, item 1 no longer touches shared code at all, which concentrates
|
||
the risk rather than spreading it: V-A6 is still *proven* by the golden suite and
|
||
not assumed, but now as a regression check over unchanged code rather than as a
|
||
hypothesis about a changed formatting path.
|
||
|
||
**Caveat on the estimate — discharged 2026-07-26.** It treated
|
||
`FrontmatterSchema.emit` as a single formatting function, inferred from its tests
|
||
rather than read. Now read: `profiles.py:197` renders one `key: value` line per
|
||
key through one `_render` helper, verbatim except for `source_query`'s
|
||
whitespace collapse. The premise held, and the sizing stands. Recorded rather
|
||
than deleted, because a premise that survives verification is only known to have
|
||
survived if the check is visible.
|
||
|
||
**Second pass, same day.** U4 re-opened the sizing from the other side — not "is
|
||
`emit` one function?" but "what shape must it emit?" — and requirement 1 records
|
||
how it closed. Both checks stay visible: the first premise survived measurement,
|
||
the second was re-opened by evidence and settled by a decision. Neither is an
|
||
estimate that was merely asserted twice.
|
||
|
||
### Shipping a provisional surface without owing stability
|
||
|
||
The install channel is a direct git reference, so consumers pin a tag explicitly
|
||
and nothing reaches anyone implicitly. The pilot therefore ships as a
|
||
**pre-release tag** (`v0.5.0a1`), and the marker lives in the tag name so a pin
|
||
is self-documenting. Three rules make the provisional status real rather than
|
||
stated:
|
||
|
||
1. `OKF_LATEST` does not point at v0.2 until GA (above).
|
||
2. The v0.2 profile's docstring and the CHANGELOG entry both say the surface may
|
||
change on pilot feedback, and name the pilot repos.
|
||
3. Breaking changes during the pilot are expected and do **not** get a
|
||
deprecation cycle. Saying this up front is what buys the freedom to act on
|
||
feedback; discovering it later is what makes a pilot a de-facto release.
|
||
|
||
### Test specifications — stated before the run, with expected results
|
||
|
||
A pilot that asks "run it and tell us how it went" returns a description. A pilot
|
||
that states the expected result first returns a *measurement*, because only then
|
||
can the consumer see that something differed. So each test below ships with its
|
||
expected outcomes numbered, plus an explicit list of what would surprise us —
|
||
naming the surprises is what lets someone else's run falsify our model instead of
|
||
merely confirming it.
|
||
|
||
All three read their inputs from this repo at the pre-release tag (`v0.5.0a1`);
|
||
nothing is transported through the mailbox except the specification itself.
|
||
|
||
#### Test A — producer path (`portfolio-optimiser-claude`)
|
||
|
||
**Procedure.** In a scratch venv, install the package at the `v0.5.0a1` tag. Take
|
||
**one manifest they have already run under `DEFAULT`**, so the existing v0.1
|
||
output is the baseline. Materialize it twice with the *same* explicit
|
||
`ingested_at`: once with `profile=DEFAULT`, once with `profile=OKF_V0_2`. Diff.
|
||
|
||
**Expected:**
|
||
- **A-E1** The `DEFAULT` run is byte-identical to what their currently pinned
|
||
version produces. Support is additive; if this fails we have broken a v0.1
|
||
consumer and the pilot stops here.
|
||
- **A-E2** The v0.2 run differs from `DEFAULT` **only in frontmatter**. Body
|
||
bytes identical, filenames identical. *Revised on po-claude's measurement:*
|
||
the original text said "only **inside** the frontmatter block", which no
|
||
bundle whose root `index.md` has no frontmatter block can satisfy — there,
|
||
A-E6 necessarily **adds** a block rather than editing within one. Adding a
|
||
frontmatter block where the profile requires one satisfies A-E2; a diff in
|
||
body bytes does not.
|
||
- **A-E3** `generated` is an inline flow mapping
|
||
`{ by: llm-ingestion-okf/<version>, at: <ingested_at> }` — not the string
|
||
`true` — and `at` equals the `ingested_at` argument **exactly**.
|
||
- **A-E4** `sources` is present as an inline flow sequence of one flow mapping —
|
||
`[{ id: <source id>, resource: <source reference> }]` — carrying at least a
|
||
`resource` derived from the manifest's source. **Corrected 2026-07-26** from
|
||
"a block list": requirement 1's re-sizing chose the flow form, for the reasons
|
||
recorded there. A test specification that outlives the decision it encoded is
|
||
the A-E6 defect class — a pilot failing our own expectation while the code is
|
||
right — so the correction lands before the pilot receives it, not after.
|
||
- **A-E5** Re-running with the same `ingested_at` is byte-identical.
|
||
- **A-E6** The root `index.md` declares `okf_version: 0.2` — **unquoted**, and
|
||
the file carries **no UTF-8 BOM**. Both are load-bearing, not stylistic:
|
||
catalog measured that a quoted value fails their shape regex `exit 1`, and
|
||
that a BOM makes the marker invisible to them while still exiting `0`. The
|
||
emitter renders values verbatim, so this is a property of the value we hand
|
||
it. Verifying the raw bytes here is part of the expectation.
|
||
|
||
**What would surprise us — report immediately:** any diff outside the frontmatter
|
||
block (the profile seam leaks); `at` differing from the `ingested_at` they passed
|
||
(a wall-clock crept in — a determinism defect); a collision refusal on a target
|
||
file that **is** theirs (`_is_ingest_owned` mis-firing under the new `generated`
|
||
shape — we predict fail-safe refusal of *foreign* files, so a false refusal of
|
||
their *own* is the failure mode); and anything about constructing the profile that
|
||
required reading our source to work out.
|
||
|
||
#### Test B — gate acceptance (`catalog`)
|
||
|
||
**Procedure.** Run their **existing, unmodified** gate on two bundles from
|
||
`examples/` at the tag: the v0.2 fixture, and the same fixture with `okf_version`
|
||
set to `"0.1"` as a control. No pin, no code change, two gate runs.
|
||
|
||
**Expected:**
|
||
- **B-E1** Both pass. The form regex accepts both values, so the two runs should
|
||
be indistinguishable.
|
||
- **B-E2** No gate emits a WARNING or ERROR mentioning the version.
|
||
- **B-E3** No gate *other than* the version gate behaves differently between the
|
||
two runs — **scoped to `okf-check.mjs`**, the one gate of their three that
|
||
takes an arbitrary bundle root. `check-okf-parity.mjs` runs over a fixed
|
||
corpus and `check-nav-golden.mjs` over their own bundle directory; neither can
|
||
see our fixture without a corpus change we excluded. Their verdict for those
|
||
two is "not run", and it must not be read as "as expected". Widening this is a
|
||
separate request with its own price, and it is the operator's call.
|
||
|
||
**What would surprise us:** any gate anywhere in their chain that keys off the
|
||
version **value** rather than its form — a membership list, an equality
|
||
comparison, a switch. Also: a gate requiring `timestamp` on a concept
|
||
(superseded in v0.2), or anything downstream of the gate — their re-pin flow,
|
||
the discovery mechanism — that assumes `0.1`.
|
||
|
||
**Correction, already measured (catalog, 2026-07-26).** Our assertion that *no*
|
||
gate keys on the value was wrong as formulated, and they falsified it before the
|
||
run rather than during it: `check-okf-parity.mjs:75` puts the **raw**
|
||
`okf_version` value into its cross-implementation signature. It *compares* the
|
||
value across implementations without *judging* it — verdict-invariant, but not
|
||
value-blind. The practical consequence for us is nil, since both implementations
|
||
read the same file and see the same value. The distinction is kept because
|
||
compare-vs-judge is precisely what B-E3 was written to settle, and recording it
|
||
imprecisely would discard the finding.
|
||
|
||
#### Test C — expressiveness (`claude-code-llm-wiki`, run by us)
|
||
|
||
**Procedure (ours).** Construct a v0.2 variant of `STRICT_V1` here, read their
|
||
bundle **read-only at a recorded commit**, validate all non-reserved documents,
|
||
send them the report **through coord**. They run nothing.
|
||
|
||
**Expected:**
|
||
- **C-E1** All **524** non-reserved documents pass the v0.2 variant while keeping
|
||
`timestamp` and emitting no `generated` — the §13.1 legacy path. The count is
|
||
theirs, given as ground truth before the run: 529 `.md` total, minus 4
|
||
`index.md`, minus 1 `README`/`log`, at HEAD `b9b557b` (ingest run
|
||
`run-20260726T053004Z`). 522 was correct at `d2c12d2` and is now stale — the
|
||
delta is exactly Claude Code v2.1.219 + v2.1.220. Reading at a fresher HEAD is
|
||
allowed provided the commit is recorded; the count that must pass is whatever
|
||
their stated ground truth says at the commit we actually read.
|
||
- **C-E2** The variant requires **no** change to their eight required keys.
|
||
- **C-E3** Everything a v0.2 posture would add is optional: `generated`,
|
||
`verified`, `status`, `stale_after`, `sources`.
|
||
- **C-E4** Their per-directory index policy and root frontmatter
|
||
(`okf_version` / `bundle_profile` / `okf_spec_commit`) are expressible
|
||
unchanged.
|
||
|
||
**What we ask them to check in our report** — the parts we cannot measure from
|
||
outside: whether any field we classified as an optional addition is in fact
|
||
load-bearing in their pipeline; whether our reading of their contract still holds
|
||
at their current HEAD; and whether "no change required" is true *operationally*
|
||
and not just formally, since they know their consumers and we do not.
|
||
|
||
### Feedback shape
|
||
|
||
Unstructured feedback is not comparable across three repos, so ask for a
|
||
**per-expectation verdict plus five things by name**.
|
||
|
||
First, one line per numbered expectation — `A-E1: as expected` / `A-E3: differed,
|
||
<what we saw>`. That is the part that makes three independent runs comparable and
|
||
that turns a disagreement into a located one. Then:
|
||
|
||
1. What was run, against which tag.
|
||
2. What failed — with the bytes or the actual error, not a summary.
|
||
3. **What was awkward but worked.** The workarounds are the highest-value signal
|
||
and the one that never shows up as a failure.
|
||
4. What was needed that the profile could not express.
|
||
5. Whether they would adopt it as-is.
|
||
|
||
**An expectation we got wrong is a better result than a clean run**, and the
|
||
request says so explicitly — otherwise a consumer who sees something odd but
|
||
passing has no reason to mention it. A clean run confirms what we already believed;
|
||
a differed expectation is the only thing that can change the design.
|
||
|
||
### The GA notification — what each repo is told to do or test
|
||
|
||
Operator directive 2026-07-26: when the v0.2 implementation is ready, every
|
||
OKF-consuming repo is told **through the coord mailbox** what they should do or
|
||
test. Not a status update — an instruction with a verdict attached.
|
||
|
||
It is a **bounded loop of directed messages, never a broadcast**: a broadcast is
|
||
inherited by every future repo, which is the wrong lifetime for a version-specific
|
||
notice.
|
||
|
||
Each message carries the same four parts: the version and its pinned upstream
|
||
commit; what we are asking of them; **what happens if they do nothing**; and the
|
||
black-box statement with its boundary. The third part is what makes the list below
|
||
honest — for most repos the answer is genuinely "nothing changes", and saying so
|
||
plainly is more useful than an invitation to review.
|
||
|
||
| Repo | What they do or test | If they do nothing |
|
||
|---|---|---|
|
||
| `portfolio-optimiser-claude` | Re-run their existing manifest under `DEFAULT` and confirm byte-identity with their frozen golden. Optionally adopt `OKF_V0_2` | Nothing changes. Their pin is explicit and `DEFAULT` is byte-stable |
|
||
| `catalog` | Re-run `okf-check.mjs` on a bundle emitting `OKF_V0_2`. Decide whether `MISSING` should keep conflating "absent", "BOM-eaten" and "MAY exercised" (U1) | Nothing changes. Their gate is version-shape-keyed, not value-keyed |
|
||
| `claude-code-llm-wiki` | Read the Test C expressiveness report (via coord, per their condition) and say whether any field we classed optional is load-bearing for them | Nothing changes. `STRICT_V1` is untouched by design |
|
||
| `portfolio-optimiser` | Re-run their golden suite against the new release. Confirm `DEFAULT` byte-stability from the sibling implementation's side | Nothing changes |
|
||
| `portfolio-optimiser-commons` | Nothing testable here — but the V1 outcome decides whether `DEFAULT`'s `generated` shape changes, and that is theirs to ratify | `DEFAULT` keeps emitting a v0.2-reserved key with a v1-era value |
|
||
| `llm-ingestion-pipeline-security` | Answer the §10 boundary questions: whether a non-`.md` code file inside a bundle is within `okf.import_bundle`'s mandate, and whether Door C should refuse bundles referencing `executor.resource` / `attester.resource` | We ship no Attested Computation fixture and implement no runtime, so the gap stays unexercised rather than unguarded |
|
||
| `okr` | Nothing at GA. The v0.2 contract reaches them in phase 4 when the Node half lifts their reference implementations | Nothing changes |
|
||
| `linkedin-studio` | Nothing. They measured zero exposure to both breaking changes and are deliberately staying at `0.1` | Nothing changes |
|
||
| `ms-ai-architect` | Nothing measured yet — the general notice went out 2026-07-26; re-derive their exposure at GA rather than assuming it is still zero | Unknown, which is why the re-derivation is on the list |
|
||
|
||
**Re-derive this table at GA rather than trusting it.** It is a premise like any
|
||
other, and consumer trees move — the wiki's document count moved by two between our
|
||
reading and their reply.
|
||
|
||
### GA exit criteria
|
||
|
||
1. Every P1–P3 finding is either fixed or explicitly accepted with the reason
|
||
recorded in this doc.
|
||
2. Golden fixtures byte-exact; V-A8 green.
|
||
3. The two existing profiles unchanged byte-for-byte, existing tests unmodified
|
||
(V-A6).
|
||
4. `mypy --strict src/`, ruff, and the boundary grep-gate clean.
|
||
5. At least one consumer has *emitted* and one has *consumed* a v0.2 bundle
|
||
end-to-end.
|
||
6. `OKF_LATEST` flips to v0.2 in its own release, with a CHANGELOG entry stating
|
||
what changed for anyone bound to the alias.
|
||
7. **Every repo in the notification table has been sent its message** and the
|
||
table was re-derived rather than reused. GA is not done when the code ships;
|
||
it is done when the consumers know what it means for them.
|
||
8. The `llm-ingestion-pipeline-security` §10 boundary questions are answered, or
|
||
their being open is recorded as an accepted, stated limit — never left silent,
|
||
since Door C is the path an external bundle's executable attester would take.
|
||
|
||
**One honest limit on what GA can mean.** A three-repo pilot exercises only what
|
||
those three repos use. `sources` with `usage_window`, multi-verifier `verified`,
|
||
and `Attested Computation` will very likely go unexercised. Those stay marked
|
||
provisional at GA rather than being silently promoted: "stable" applies to the
|
||
surface the pilot actually covered, and claiming more would be the same
|
||
unearned-claim pattern that "conform first, claim after" exists to prevent.
|
||
|
||
## Open questions
|
||
|
||
**V1 — `generated`'s shape in `DEFAULT` (commons').** *No longer blocks our v0.2
|
||
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. Commons
|
||
confirmed authorship, verified our four premises against `okf/SPEC.md`
|
||
independently, and put it in their ratification queue as item 8. They are not
|
||
blocking us and we are not waiting on them.
|
||
|
||
**Our recommendation changed on their finding, 2026-07-26.** We carried
|
||
`generated: { by: "llm-ingestion-okf/<v>", at: <ingested_at> }` — call it (b).
|
||
Commons found that it collides with ingest-spec's own conformance clause: `:29`
|
||
requires a conformant implementation to reproduce the shared golden extractions
|
||
byte for byte, `:267` compares `expected-bundle/` byte for byte, and `generated`
|
||
sits inside the mandatory ordered prefix (`:149-152`). A producer's name and
|
||
version inside a *shared* fixture means no other conformant implementation can
|
||
reproduce it, and Golden regression fires on every version bump without any
|
||
contract having changed.
|
||
|
||
They measured the blast radius as normative-only for them —
|
||
`examples/ingest-golden-*` does not exist in commons. **It is observable here
|
||
today.** Four fixtures carry `generated:` on line 8 inside the byte-compared
|
||
prefix (`ingest-golden-file/…/ingest-orders.md`, `…/ingest-products.md`,
|
||
`ingest-golden-sql/…/ingest-metrics.md`, `ingest-golden-http/…/ingest-status.md`).
|
||
|
||
**The `:29` argument survives, but not in the form we sent it.** We wrote that
|
||
`ingest-golden-file` is the fixture *shared* with the sibling implementation.
|
||
Commons checked, and it is not: our set and po-claude's are disjoint, and commons
|
||
has none — `git log --all` confirms they never did. `:29` requires reproducing
|
||
"**the shared** golden extractions byte for byte", and the definite article
|
||
presumes one set where there are two, so the clause has no referent today and each
|
||
implementation reproduces its own by construction.
|
||
|
||
Their reformulation is stronger than our original: (b) does not break a shared
|
||
fixture we have — **(b) cements that a shared fixture can never arise**, because a
|
||
producer-bearing field inside the byte-compared prefix makes convergence
|
||
impossible by construction, later as much as now. The `:280` half stays observable
|
||
here and fires on our next release either way.
|
||
|
||
So we now back **(d)**, commons' fourth option: `generated: { by:
|
||
"process:<fixed id>", at: <ingested_at> }`, using v0.2 §7's explicit
|
||
`process:<id>` actor form. Two measured reasons, the second visible only from
|
||
this side:
|
||
|
||
1. The value becomes constant, so the shared fixture stays producer-neutral and
|
||
version-stable.
|
||
2. `_is_ingest_owned` (`materialize.py:131-150`) stays an equality check. Under
|
||
(b), `<our actor>` carries a version, so the collision gate must either
|
||
prefix-match and ignore the version — parsing a value we had just made
|
||
structured — or stop recognizing files an earlier version wrote, which fails
|
||
safe but breaks re-running the same manifest across a version bump.
|
||
|
||
**The honest cost of (d), stated so the option set is not skewed our way:**
|
||
producer identity leaves the bundle. `ingest_manifest` still names the manifest
|
||
and §8 logging still holds the producer here, but a log does not travel with the
|
||
bundle through Door C. If tool-level traceability inside the artifact is a
|
||
requirement, (b) is right and `:29` is the price to ratify deliberately.
|
||
|
||
Either way the decision is the operator's via commons' queue, not ours. Our only
|
||
ask: if (d) is ratified, we want it before freezing a v0.2 fixture carrying
|
||
`generated`, so the regeneration happens once.
|
||
|
||
**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.
|
||
|
||
**V6 — Door C imports the pointer but never the code it points at.** Raised with
|
||
`llm-ingestion-pipeline-security`; the decision is theirs.
|
||
|
||
Measured 2026-07-26, in our own code: **no door can create, import, or persist an
|
||
executable file.** Door C walks `rglob("*")` and skips every suffix that is not
|
||
`.md` (`importer.py:226-231`, an explicit case-folded test rather than a `*.md`
|
||
glob, since glob case-sensitivity follows the filesystem). Door B resolves an
|
||
extractor by suffix and `.py` is in neither the core set (`md`/`txt`/`csv`/`json`/
|
||
`html`) nor `_OPTIONAL_EXTENSIONS` (`pdf`/`docx`/`xlsx`), so it fails fast
|
||
(`extract.py:113,131-142`). Door A writes only `ingest-*.md`.
|
||
|
||
That is a stronger posture than we first told the guard — and we told them wrongly,
|
||
asserting that a third-party attester `.py` could reach a consumer's tree through
|
||
our door. It cannot. The claim was reasoned, not measured, and it was corrected the
|
||
same hour.
|
||
|
||
**The real exposure is the inverse, and it is ours before it is theirs.** Door C
|
||
writes a merged concept **verbatim**, frontmatter included. Under v0.2 that
|
||
frontmatter may carry `executor.resource` and `attester.resource`. So we import the
|
||
*reference* to executable code while never importing the code. Two outcomes:
|
||
|
||
1. The pointer dangles. A consumer following it fails — noisy, safe.
|
||
2. **The pointer resolves to something else.** The path is relative to the bundle
|
||
root, so if the destination tree already holds — or later gains — a file at
|
||
`attesters/sql_equality.py`, the imported concept now references code it never
|
||
shipped. Not a broken reference: a substituted one, and it looks valid.
|
||
|
||
Outcome 2 is the one worth attention. We have not measured collision likelihood;
|
||
the reference bundle's path names (`attesters/`, `skills/`) are generic enough that
|
||
"unlikely" would be an assertion, not a finding.
|
||
|
||
**The structural point we missed first time round.** The guard is a *persist* gate:
|
||
"is this content safe to persist?". Executable code carries its risk at
|
||
**execution**, not at persistence — a `.py` entirely safe to store may be dangerous
|
||
to run. So even a maximally strict persist gate does not cover the class §10
|
||
introduces, and the execution boundary has **no owner in this stack today**,
|
||
because upstream deferred it ("the attester ABI, portability, and sandboxing") and
|
||
we declared runtime out of scope on that same deferral. Recorded because "the guard
|
||
covers it" is a conclusion someone could reasonably draw and it would be wrong in a
|
||
way that is hard to detect later.
|
||
|
||
Our leaning, for them to accept or overrule: Door C should **refuse** a concept
|
||
whose frontmatter references an executor or attester resource that did not come
|
||
with it — failing closed, and half an Attested Computation is not something a
|
||
consumer can use anyway.
|
||
|
||
## TDD order
|
||
|
||
Steps 1–3 are almost entirely tests, so they are safe first and de-risk
|
||
everything after. **Done 2026-07-26** (`1215f98`, `7bc366b`).
|
||
|
||
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 converts today's fail-safe from an accident into a
|
||
documented guarantee.
|
||
2. **Audit Door C against the tolerance rules** (D3): one test per MUST NOT plus
|
||
the bare-`verified` MUST. Failures here are real defects, not plan revisions.
|
||
*Measured: all six pass.* Door C's tolerance is structural rather than
|
||
lenient — it writes the guard's bytes verbatim and never parses the sender's
|
||
frontmatter, so a D1b reader that starts judging shape at this door is what
|
||
would break them.
|
||
3. **Assert no hard-coded version** anywhere a profile should decide (V4), and
|
||
the no-`timestamp`-plus-malformed-`generated` construction invariant (V-A7).
|
||
**The one place these three steps add behavior**, and it was an operator
|
||
decision to add it here rather than at D2: V-A7's stated test shape is a
|
||
construction-time refusal, and building it before D2 is what develops D2
|
||
under the invariant instead of alongside it. It lives on
|
||
`FrontmatterSchema.__post_init__` — the C3 analog, and a profile must build
|
||
its schema first. Behavior-neutral for both shipped profiles, with the
|
||
byte-exact golden suite as the proof rather than the claim.
|
||
4. **D1a — the emitter half only.** `FrontmatterSchema.emit` accepts a block-list
|
||
value. Runs through `DEFAULT`'s path, so the golden suite proves V-A6 here.
|
||
**No longer tag-blocking** after requirement 1's re-sizing — the flow form
|
||
needs no emitter change — so this stays here for D1/Door C and drops out of
|
||
the critical path to `v0.5.0a1`.
|
||
5. **D2** — the `OKF_V0_2` profile, profile-aware `_is_ingest_owned`, `sources`
|
||
derivation. `OKF_LATEST` stays on v0.1 semantics.
|
||
6. **D5** — the v0.2 golden fixture, generated by the code. `okf_version: 0.2`
|
||
is declared only here, once 1–5 are green (conform first, claim after).
|
||
**Unquoted** — the quoted form written here previously contradicted A-E6 and
|
||
is exactly what catalog measured as `exit 1`.
|
||
7. **P1** — read-only sweep over real pilot bundles. Cheap, zero-risk, and the
|
||
first point where a finding can come from outside our own assumptions.
|
||
8. **Pre-release tag `v0.5.0a1`**, then **P2** (producer) and **P3** (gate).
|
||
Steps 1–8 are the whole pilot scope.
|
||
9. **P4** — address findings.
|
||
10. **D1b — the structured reader** (V2's decision applies here, not earlier),
|
||
then **D3** — Door C conformance against the tolerance rules. This is where
|
||
consuming third-party v0.2 bundles becomes real; no pilot repo sends us one,
|
||
so it deliberately follows the feedback rather than preceding it.
|
||
11. **D4** — `Attested Computation` round-trip. Nothing in the pilot set uses it,
|
||
so building it earlier would add surface the feedback cannot reach.
|
||
12. **GA** — flip `OKF_LATEST`, its own release. **V1 to commons** in parallel
|
||
throughout; **D6** lands with the GA release.
|
||
|
||
## Key assumptions, each with its test
|
||
|
||
| # | Assumption | Test |
|
||
|---|---|---|
|
||
| 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 the scalar parser unmodified | Step 1 characterization; byte-exact round-trip |
|
||
| 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 |
|
||
| V-A5 | No profile hard-codes an upstream version | Step 3 |
|
||
| 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
|
||
|
||
- Implementing attestation execution — executors, attesters, receipts, verdicts
|
||
(D4, on upstream's deferral).
|
||
- Migrating `DEFAULT` or `STRICT_V1` onto v0.2. Support is additive; those two
|
||
contracts belong to commons and the wiki.
|
||
- Editing commons' ingest-spec locally. V1 is raised there.
|
||
- Declaring `okf_version: 0.2` on any bundle whose shape has not earned it.
|
||
- Config-file/DSL profile loading — still a phase-3 extension point.
|
||
|
||
## Coordination — who needs to know what
|
||
|
||
- **commons** — V1. `generated`'s shape in ingest-spec §5. No longer blocking us,
|
||
still theirs to decide.
|
||
- **catalog** — form owner. Upstream moved to v0.2; their form-not-membership
|
||
gate absorbs the bump with no change; V4 is theirs; and a new profile now
|
||
declares `"0.2"`, which is the first bundle shape in this repo to do so.
|
||
- **wiki** — informational. Their `timestamp` is legacy-but-readable under
|
||
§13.1; nothing 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 ports a
|
||
contract that already knows about v0.2 rather than a v0.1 shape.
|
||
|
||
## Verification
|
||
|
||
1. Spec re-read at a pinned commit; hash recorded in this doc.
|
||
2. `pytest` green; `mypy --strict src/`; `ruff check .` and
|
||
`ruff format --check .` clean.
|
||
3. V-A6: golden suite byte-for-byte under both existing profiles; zero fixture
|
||
changes outside the new v0.2 fixtures (`git diff --stat examples/` shows
|
||
additions only).
|
||
4. V-A7 and V-A5 named tests present and failing-by-construction if removed.
|
||
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, and it
|
||
points at `docs/upstream-okf-upgrade-runbook.md` — the procedure the standing
|
||
always-latest policy runs on, written after this round precisely because the
|
||
spec was read and the shipped example bundles were not.
|