The notice po-claude was promised "before GA, not with it" went out 2026-08-02. Two places said otherwise and are now false in a tracked public doc: the "Owed and unsent" paragraph and GA criterion 9's tail. The paragraph also records what the message corrected rather than only what it announced: our 2026-07-31 advice against binding to OwnershipPolicy.owns stands, but the reason given then (it costs them a dependency) was not the load-bearing one -- phase 4's Node half is. A consumer building on the wrong reason builds the right thing by accident. Criterion 9 stays as the GA backstop; only its notice half is closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JxRjDS9U2szUGYDeuZB2M3
1593 lines
97 KiB
Markdown
1593 lines
97 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.
|
||
|
||
**Narrowed 2026-07-27: the discharge covers frontmatter, not bodies.** U8 records
|
||
a §5.1 mechanism — per-claim footnote attribution — that sits in the pinned
|
||
`SPEC.md` and in the shipped bundle bodies, and that this enumeration missed. The
|
||
heading stays "DISCHARGED" because the pin itself holds and every fact below was
|
||
read at it; what failed was the reading's *depth*, and the correction is to say
|
||
so rather than to quietly widen the claim. Practical rule taken forward: a bundle
|
||
is frontmatter **and** body, and "we read the bundles" is not evidence that we
|
||
read either half in particular. The cheapest form of this check is a local clone
|
||
at the pin, where a grep covers the tree instead of the files we thought to
|
||
fetch — see the runbook.
|
||
|
||
## 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 — The undocumented keys in the reference bundle. RESOLVED 2026-07-27
|
||
|
||
`metrics/gross-margin.md` carries a `not:` family (`- term / why / instead`) that
|
||
we could not locate in the spec sections read. The question recorded here was
|
||
whether it is a v0.1 field we never enumerated or an undocumented one.
|
||
|
||
**Measured, and it is neither of the two things that would have mattered.** Zero
|
||
occurrences in the v0.2 `SPEC.md`; zero in the **v0.1** `SPEC.md` (451 lines, read
|
||
at the parent of the v0.2 migration commit `780fe9d`); no reference anywhere in
|
||
upstream's own `reference_agent` source; and exactly **one** file in the whole
|
||
bundle set uses it. So it is an undocumented, hand-authored key in a single
|
||
example — legal under §14's "unknown additional keys MUST NOT be rejected", and a
|
||
demonstration of the open namespace rather than a field. Nothing to support.
|
||
|
||
The same sweep settles a second one. `# Cited by` appears as a body section in two
|
||
`policies/` files and nowhere in the spec — but `viewer/templates/viz.html:56`
|
||
renders a "Cited by" heading, so the visualizer **derives** that view by inverting
|
||
`sources` across the bundle. The two files hand-author what a consumer computes.
|
||
|
||
Both fit one upstream pattern worth keeping in view, because it is the same
|
||
principle stated for credibility scores and trust tiers: **OKF stores signals and
|
||
consumers derive views.** A reverse-citation index is a view. Nothing here is
|
||
something a producer emits, so none of it reaches our doors.
|
||
|
||
Method note, since it is the point of U8: answering this needed the v0.1 spec
|
||
text, upstream's source, and a grep over the whole tree at once. All three became
|
||
available when the pinned tree was cloned locally (operator, 2026-07-27) instead
|
||
of fetched file by file.
|
||
|
||
### U8 — Per-claim attribution is a BODY convention, and we read only frontmatter
|
||
|
||
Added 2026-07-27. `sources` does not replace `# Citations` on its own: §5.1 binds
|
||
a claim to a source with a markdown footnote whose label is a `sources[].id`, and
|
||
`bundles/acme_retail/tables/orders.md:31-36` ships exactly that — footnote
|
||
references inside a schema table.
|
||
|
||
**This was measurable at the pinned commit the whole time.** It surfaced from
|
||
Google's launch blog post on 2026-07-27, but the blog is not the source: the
|
||
convention is in the `SPEC.md` we had already pinned and read. U1–U7 measured
|
||
frontmatter in all four reference bundles and walked past the bodies, so the
|
||
enumeration was frontmatter-deep rather than complete. The runbook's rule — read
|
||
the shipped bundles, not only the spec — was followed in letter and half in
|
||
substance, because a bundle is frontmatter *and* body.
|
||
|
||
What it changes, and what it does not:
|
||
|
||
- **Nothing in emission.** Door A renders tables and fenced blocks and writes no
|
||
citations at all; Door C writes bodies verbatim. There is no defect here.
|
||
- **The exposure advice, which we had already sent.** The v0.2 exposure report to
|
||
the wiki (2026-07-26) states the supersession as "`# Citations` → `sources`"
|
||
under the heading that exists to size their cost. Accurate and incomplete; a
|
||
correction is owed and is the same class as A-E3's.
|
||
- **V-A1's discharge is narrower than it reads.** See the provenance section.
|
||
|
||
### 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` |
|
||
|
||
**The `# Citations` row states where provenance goes, and that is only half the
|
||
supersession.** Per-claim attribution moves to a **markdown footnote in the body
|
||
whose label is a `sources[].id`** (§4.2 `:224-225`, §5.1 `:347-360`), and the
|
||
labels are keyed rather than positional precisely because agents rewrite these
|
||
documents — a positional index misattributes silently the moment the list is
|
||
reordered. So a consumer who moves a `# Citations` list into `sources` and stops
|
||
there has migrated the list and dropped the claim→source binding. No RFC-2119
|
||
keyword attaches to the footnote form itself; §13.1's SHOULD/MAY governs the
|
||
supersession. Recorded 2026-07-27 — see U8.
|
||
|
||
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.
|
||
|
||
**Done 2026-07-31** (`deeb248`, `c6d64c3`), in three parts and with one part
|
||
deliberately left undone:
|
||
|
||
- The five contract fields join `OKF_V0_2`'s emission order as one block, in
|
||
§10.2's own listing order. Without it they still emitted — in `emit`'s sorted
|
||
tail, where `attester` precedes `runtime`: alphabetical order standing in for
|
||
the contract's own.
|
||
- `FrontmatterSchema.required_by_type` carries §10.2's one requirement
|
||
(`runtime`, for this type and no other). It is the first rule here keyed off a
|
||
frontmatter *value* rather than a key, and it cannot be `required`, which would
|
||
demand `runtime` of every document. A type the mapping does not name carries no
|
||
extra requirement — which is what keeps the field inside §14, since a consumer
|
||
must not reject on an unknown `type`.
|
||
- Door C reports the §10 pointers it imports (V6, below).
|
||
- **Not done, and not a gap: reading the canonical block form.** §10.2 presents
|
||
`executor` and `attester` as nested block mappings, both carrying a `resource`.
|
||
Measured: the line-oriented parser has no indentation model, flattens them into
|
||
one namespace, and the second `resource` overwrites the first — `executor.resource`
|
||
is lost with no error raised. Pinned as a characterization test. The invariant
|
||
that falls out and now sits in `CLAUDE.md`: **we emit flow form, never block**,
|
||
or we write bundles we cannot read back. Reading block is D1b's.
|
||
|
||
**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.
|
||
|
||
### P1 results — measured 2026-07-31, before the tag
|
||
|
||
Read-only throughout: nothing was written into a consumer tree, and no consumer
|
||
ran anything. Corpora and the commits they were read at:
|
||
|
||
| Corpus | Bundles | Concept docs | Read at |
|
||
|---|---|---|---|
|
||
| ours (`examples/`) | 4 | 5 | `2504011` |
|
||
| `portfolio-optimiser-claude` | 4 | 11 | `8a14137` |
|
||
| `catalog` (`test/okf-parity-corpus/green-*`) | 4 | 6 | `3dfc513` |
|
||
| `claude-code-llm-wiki` (`bundle/`) | 1 | 526 | `c5141f8` |
|
||
| upstream reference bundles (`okf/bundles/`) | 4 | 53 + one `log.md` | `3fcbb9f` |
|
||
|
||
Upstream is **beyond the pilot set** and was added deliberately: it is the only
|
||
corpus authored by the version's own publisher, and it is what turned the first
|
||
finding from an opinion into a measurement. `node_modules` and `.obsidian` were
|
||
excluded from the listing, which is the caller's call to make — the library
|
||
enumerates nothing.
|
||
|
||
Every finding below is on the **judge** path. None of them touches what the
|
||
library emits, so none of them blocks `v0.5.0a1`, P2 or P3: both remaining pilot
|
||
tests are emit-side. They are recorded here against GA exit criterion 1.
|
||
|
||
**Disposition, decided by the operator 2026-07-31: F1 is fixed before the tag,
|
||
the other five are not.** The line between them is whether the finding bites in
|
||
code the pilots will actually pin. F1 does — `IndexPolicy.violations` is public,
|
||
`OKF_V0_2` names the key, and the profile contradicted its own emitter. The
|
||
others do not: F2 and F3 need a value model the parser does not have (which is
|
||
D1b, already scheduled), and the surface where they would bite — a read path
|
||
built on `TypePolicy.allowed` — is not wired to any profile. Measured: all three
|
||
call sites (`manifest.py:202`, `inbox.py:105`, `inbox.py:245`) hard-code
|
||
`DEFAULT.types`, and both `DEFAULT` and `OKF_V0_2` set `allowed=None`, so the
|
||
closed-set branch is unreachable in shipped code and fires only for a caller
|
||
that constructs `STRICT_V1` and calls `rejection` directly — which is what this
|
||
sweep did. Patching `TypePolicy` to dequote would therefore add a repair to a
|
||
write path that never sees a quote, and would fix the meaning of a quote without
|
||
a value model able to express one — precisely what commons warns against for a
|
||
line-oriented format (`method-spec.md:90`). F2, F3 and F4 are one root cause and
|
||
go to D1b together; F5 and F6 stay P4's.
|
||
|
||
**P1-F1 — `root_frontmatter` conflates "permitted and ordered" with "required",
|
||
and the emitter and the judge disagree about the same key.** `IndexPolicy`
|
||
reports `index_root_key_missing` for every key in `root_frontmatter` absent from
|
||
the root index, so `OKF_V0_2` (which names `okf_version`) judges any bundle that
|
||
exercises upstream's MAY-by-omission as violating. Measured: **14 of the 17
|
||
bundles swept fail, each with exactly this one violation** — including all four
|
||
of upstream's own reference bundles and three of our four goldens. The three
|
||
that pass are the three carrying the key. Meanwhile D5's emitter treats it as
|
||
optional: omitting `root_frontmatter_values` writes no block. We emit a MAY
|
||
correctly and grade it as a MUST. `STRICT_V1` is unaffected in substance — the
|
||
wiki emits all three of its pinned keys (measured) — but it is the same field
|
||
carrying two meanings, so the fix has to separate them rather than relax them.
|
||
|
||
**FIXED before the tag.** `IndexPolicy` gained `root_frontmatter_required`:
|
||
`root_frontmatter` permits a key and fixes its position, the new field demands
|
||
it, and a required key outside the ordered set now fails at construction.
|
||
`OKF_V0_2` leaves it empty (upstream's MAY), `STRICT_V1` names all three
|
||
(unchanged behavior for the proving consumer), `DEFAULT` is untouched.
|
||
Re-measured over the eight bundle-root indexes reachable locally — our four
|
||
goldens and upstream's four: **7 of 8 failing under the old semantics, 0 of 8
|
||
after**, matching this finding's own numbers. The emit path is byte-identical;
|
||
the golden suite would have caught it otherwise.
|
||
|
||
**P1-F2 — `TypePolicy.allowed` cannot be used against a consumer that quotes its
|
||
scalars.** All **526/526** wiki documents are rejected as `okf_type_not_allowed`
|
||
under `STRICT_V1`; **526/526** are clean when values are dequoted before judging.
|
||
The bundle quotes every value (5265 of 5267), and this library's line-oriented
|
||
parser has no scalar value model, so the quotes are part of the value. Blast
|
||
radius is exactly `TypePolicy.allowed`, because it is the only surface that
|
||
judges a value at all — `FrontmatterSchema` judges keys and value *shape*. The
|
||
suite cannot see this: its tests hand-build the mapping and never parse a file.
|
||
|
||
**P1-F3 — `FrontmatterSchema.nullable` is unreachable from a parsed document.**
|
||
Two wiki documents carry `summary: null`; the parser yields the string `"null"`,
|
||
which is a legal value, so `frontmatter_value_null` can never fire and a real
|
||
null is indistinguishable from the literal text. `STRICT_V1`'s
|
||
`nullable={"summary"}` is dead configuration on the read path. Same root cause
|
||
as F2, and worth stating separately because a fix for one does not imply the
|
||
other.
|
||
|
||
**P1-F4 — the line parser manufactures false top-level keys from real v0.2 block
|
||
YAML, and the count is large.** Over upstream's 54 documents it extracts 12
|
||
nesting-derived keys (`resource` 51, `by` 44, `at` 44, `id` 32, `author` 5,
|
||
`last_modified` 5, …) and 6 dash-prefixed artifacts (`- id` 22, `- title` 19,
|
||
`- resource` 16, `- { by` 8, `- { name` 2, `- term` 1), while the container keys
|
||
read as empty (`sources` 49/49, `generated` 44/53, `verified` 8/8). Consequence,
|
||
measured rather than predicted: `STRICT_V1`'s schema produces **444 spurious
|
||
findings** over those 54 documents (376 `frontmatter_key_not_allowed`, 68
|
||
`frontmatter_key_malformed`). This is U4 on the read path with a number attached,
|
||
and it is the case for D1b: **no closed-namespace profile should be pointed at a
|
||
real v0.2 bundle until the structured reader lands.**
|
||
|
||
**P1-F5 — the door-side type gate is not a read-path judgement, and its docstring
|
||
reads as though it were.** `TypePolicy.rejection` refuses the reserved `verdict`
|
||
layer and is called only from `manifest.py:202` and `inbox.py:105`/`:245` — write
|
||
paths both; Door C never calls it. Pointed at existing bundles it labels
|
||
legitimately promoted verdict files as violations, which is what happened on two
|
||
real po-claude bundles (one document each). Not a defect in the doors: a gap in
|
||
the read surface, which does not exist, plus a docstring that invites the
|
||
confusion by saying "which `okf_type` values a bundle admits".
|
||
|
||
**P1-F6 — v0.2 reserves two filenames and the profile models one.** §9/§12 make
|
||
`log.md` reserved and let it appear at any level; `IndexPolicy` names only
|
||
`index.md`, and no profile carries a reserved-file set. Measured on upstream's
|
||
`acme_retail`: it ships a root `log.md` (`type: Log`) and its root index does
|
||
**not** list it. So a caller building the listing an `entries_match_directory`
|
||
profile requires has to know to exclude reserved names, and this library tells
|
||
them only about `index.md`. Consistent with "the library enumerates nothing" —
|
||
but the caller is being asked to know something we hold and do not state.
|
||
|
||
**What the sweep confirmed, which matters as much as what it found:**
|
||
|
||
- **V-A3 holds against real third-party v0.2 data.** No `generated` value in
|
||
upstream's bundles reads as owned: 44 empty (block form), 9 someone else's
|
||
inline flow mapping, 1 absent. A real v0.2 bundle cannot talk the collision
|
||
gate into overwriting.
|
||
- **`OKF_V0_2.frontmatter` reports zero violations across all 602 documents in
|
||
all five corpora** — with F4's caveat that an open namespace is a weak test.
|
||
- **Test C — C-E1 differed, C-E2/C-E3/C-E4 as expected.** C-E1: 0/526 pass as
|
||
parsed, 526/526 pass dequoted, single cause, F2. C-E2: exactly two key-order
|
||
signatures across 526 documents (173× the eight required keys, 353× those plus
|
||
the three layer-specific ones), both prefixes of `_STRICT_V1_KEY_ORDER` in
|
||
order — no change required to their eight. C-E3: not one document carries
|
||
`generated`, `sources`, `verified`, `status` or `stale_after`, and `timestamp`
|
||
is on 526/526, so the §13.1 legacy path holds. C-E4: the root index carries
|
||
exactly its three pinned keys in the pinned order, nested indexes carry none,
|
||
and `entries_match_directory` is clean in both directions at `c5141f8`.
|
||
- **A count correction owed to the wiki.** Their pre-run ground truth was "529
|
||
`.md`, minus 4 `index.md`, minus 1 `README`/log = 524" at `b9b557b`. Measured
|
||
at that commit: **528** `.md` under `bundle/`, **3** files named exactly
|
||
`index.md`, 1 `README.md` — 524 is right, and both terms of the decomposition
|
||
are off by one in a way that cancels. The fourth "index" is a concept whose
|
||
filename ends in `-index.md`. At `c5141f8`, the same arithmetic gives **526**,
|
||
which is what the sweep judged.
|
||
|
||
### 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.
|
||
|
||
**Measured against the guard 2026-07-26, and it settles the form question by
|
||
removing it.** The guard's T2 frontmatter parser (Door C, `okf.import_bundle`)
|
||
refuses every route to a mapping, each on its own rule: the flow form on the
|
||
disallowed-indicator set (`{` and `[` are both in it), the block form on the
|
||
nested-mapping check, and dotted `generated.by` on the key pattern. What
|
||
survives is scalars and flat lists of strings. So a `generated` **mapping** —
|
||
which is what v0.2 specifies — has no expressible form through that gate at
|
||
guard 0.2.0, and the flow form is not worse than the alternatives; there are
|
||
none. Requirement 1 therefore stands as chosen, on its three original grounds.
|
||
|
||
What the measurement does change is a claim, not a form: nothing in this tag
|
||
may state that a v0.2 bundle we emit round-trips through a guard-gated import.
|
||
The profile documents that as a measured limitation. Door B is unaffected —
|
||
the same six shapes through `screen_output` all return `warn`, and no Door B
|
||
module references `parse_frontmatter` — so the grammar binds what can be
|
||
IMPORTED, never what we emit. Measured against 0.2.0, the version we pin; the
|
||
guard reports different behaviour on a later one, which is theirs to state.
|
||
|
||
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: process:okf-ingest, at: <ingested_at> }` — not the string
|
||
`true` — and `at` equals the `ingested_at` argument **exactly**.
|
||
|
||
**Corrected twice, both times before the pilot receives the tag.** First
|
||
2026-07-27: the original read `{ by: llm-ingestion-okf/<version>, ... }`,
|
||
written before V1 resolved, and the actor lost its version — a producer
|
||
version inside a byte-compared golden fires regression on every release with
|
||
no contract having changed.
|
||
|
||
Then **2026-07-31, and this one changed the actor itself.** Commons decided
|
||
`<fast id>` = `process:okf-ingest` on our own proposal, explicitly excluding
|
||
option (d) (`process:llm-ingestion-okf`) that we had shipped in D2. The
|
||
exclusion is `ingest-spec.md:7-8`, frozen on the spec being framework-neutral:
|
||
normalising *our* repo name would force every other conformant implementation
|
||
to write it into its own output. This landed in code, in the byte-compared
|
||
fixture, and here **before any pilot was notified** — `v0.5.0a1` carried the
|
||
excluded id and was abandoned unused for `v0.5.0a2`.
|
||
|
||
Catching it late would have been expensive in a specific way worth recording:
|
||
`actor` is both the stamp written and the value owned back
|
||
(`OwnershipPolicy`), and recognition is one-way. A pilot that had run Test A
|
||
against the excluded id would hold bundles this library stops recognising the
|
||
moment the id is corrected — `collision_unstamped` on their *own* files, which
|
||
is exactly the A-E5 failure mode, inflicted by us.
|
||
|
||
A pilot expectation that outlives its own decision is the A-E6 defect class,
|
||
so both corrections are sent to po-claude rather than left to surface during
|
||
their run.
|
||
- **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 — and the
|
||
second `OKF_V0_2` run goes **into the directory the first one wrote**, not into
|
||
a fresh one. **Sharpened 2026-07-26 on po-claude's finding**, which located the
|
||
hole in A-E1: the emitter (`materialize.py:164`, `"generated": "true"`) and the
|
||
ownership predicate (`:144`, `frontmatter.get("generated") != "true"`) are
|
||
coupled through that string literal, and `OKF_V0_2` changes exactly that value.
|
||
Change the emitted form without the predicate and the library stops recognising
|
||
its own output, so the collision gate fires `collision_unstamped` on the files
|
||
its previous run wrote. One run compared byte for byte — A-E1's shape — cannot
|
||
see that: the defect needs two successive runs into the same directory. So the
|
||
expectation is not only byte-identity but that the second run **succeeds**.
|
||
Verified on `main`, not only on their pinned v0.3.2: the coupling is unchanged
|
||
there, `_is_ingest_owned` having since gained a per-manifest stem check that
|
||
narrows ownership further without touching the literal.
|
||
|
||
Distinct from this, and **not** a pilot expectation because the requirement is
|
||
undecided: whether a `DEFAULT`-written bundle may be re-run **in place** under
|
||
`OKF_V0_2` — i.e. whether the predicate accepts both the v0.1 stamp and the
|
||
v0.2 form. That is a D2 requirement question standing with the operator.
|
||
- **A-E6** The root `index.md` declares `okf_version: 0.2` in a **frontmatter
|
||
block opening the file** — the first bytes are `---\nokf_version: 0.2\n---\n\n`
|
||
— with the value **unquoted** and **no UTF-8 BOM**.
|
||
|
||
**PLACEMENT — settled 2026-07-31, frontmatter.** Catalog read upstream
|
||
themselves at the pinned commit `3fcbb9f` and reported §8:509-510 ("Index files
|
||
contain no frontmatter, with one exception: a bundle-root `index.md` MAY carry
|
||
an `okf_version` key") and §12:773-775 (declared "in a bundle-root `index.md`
|
||
frontmatter block (the only place frontmatter is permitted in an `index.md`)").
|
||
Their own spec (`:55`, `:95`) says the opposite about the same file; the
|
||
divergence is real, it is **theirs against upstream**, and its resolution is
|
||
their operator's. We conform to upstream. D5 is frozen on that.
|
||
|
||
**The two byte properties are expectations about DIFFERENT things, and A-E6
|
||
must not conflate them** — doing so would be this expectation's own defect
|
||
class:
|
||
|
||
- **Unquoted** is not a property of OKF v0.2 either way. The expectation
|
||
stands; its ORIGINAL REASON DID NOT SURVIVE 2026-07-31, and the reason is
|
||
recorded here rather than quietly swapped because reading a stale rationale
|
||
as current is this expectation's own defect class.
|
||
|
||
*What it said, and why that is now wrong.* It said unquoted was a property
|
||
of **catalog's gate**: their shape regex `/^\d+(\.\d+)*$/` read the quotes
|
||
as part of the value and exited 1, which meant the gate rejected upstream's
|
||
§12:773 — the only line in the whole spec showing the key *with* a value,
|
||
and it shows it **quoted**. We called that catalog's defect to resolve, and
|
||
they resolved it: **from spec 0.3 (`1ca27f6`) the value is unquoted before
|
||
the form check, so `okf_version: 0.2` and `okf_version: "0.2"` both pass.**
|
||
"An unquoted value is what passes" is no longer a discriminating statement
|
||
about their gate.
|
||
|
||
*Why the expectation survives anyway.* Commons settled the same question
|
||
from the other side on 2026-07-31: `method-spec.md:90` (frozen since
|
||
`7d2b46c`) parses frontmatter line-oriented as `key: value` strings, so a
|
||
quote would be **a character in the value, not syntax a parser removes** —
|
||
there is no transparent quoting in the format at all. Upstream emits the
|
||
same way (`by: human:jsmith@acme`, colon in the value and no quotes). So we
|
||
emit unquoted because that is the format's own convention, not because a
|
||
consumer's gate would otherwise reject it.
|
||
- **BOM-free** is a property of the file we emit, full stop. A BOM makes the
|
||
marker invisible to their gate *while still exiting 0* — a failure that
|
||
reports success.
|
||
|
||
Both are asserted on **raw bytes**, never on a parsed value. **The reason given
|
||
here was half wrong, measured 2026-07-31 under V-A8, and the correction matters
|
||
more than the sentence did.**
|
||
|
||
*What it said:* "`yaml.safe_load` returns `"0.2"` whether or not it was quoted
|
||
and strips a BOM before any caller sees it, so a parsed assertion masks precisely
|
||
these two defects."
|
||
|
||
*Measured against PyYAML 6.0.3, which satisfies the `pyyaml>=6.0` that
|
||
upstream's own `pyproject.toml` requires:*
|
||
|
||
| Input | `safe_load` returns | Type |
|
||
|---|---|---|
|
||
| `okf_version: 0.2` | `0.2` | **float** |
|
||
| `okf_version: "0.2"` | `'0.2'` | str |
|
||
| BOM + `okf_version: 0.2` | `{'okf_version': 0.2}` | BOM stripped, key clean |
|
||
|
||
So the **BOM half is true** — a parsed assertion genuinely cannot see a BOM, and
|
||
that is the whole reason the BOM expectation is a byte assertion. The **quoting
|
||
half is false**: the two forms are not merely distinguishable after parsing, they
|
||
come back as *different types*. Raw-byte assertion remains the right call for
|
||
quoting too — the two consumers that actually read this key parse line-oriented,
|
||
so bytes are what they see — but it is right for that reason, not because parsing
|
||
cannot tell.
|
||
|
||
**The correction carries a finding the original claim was hiding**, and it is
|
||
about the value space rather than about this fixture:
|
||
|
||
- Unquoted, `okf_version: 0.10` loads as `0.1` — **indistinguishable from v0.1**
|
||
to any YAML-parsing consumer. Quoted, `'0.10' != '0.1'`.
|
||
- Unquoted, the *type* is not even stable across version shapes: `0.2` is a
|
||
float, `0.2.1` is a str.
|
||
- Upstream's only written instance of the key (`SPEC.md:773` @ `3fcbb9f`) is
|
||
**quoted**: `okf_version: "0.2"`. None of its four shipped bundles emit the key
|
||
at all, so the spec line is the entire prior art.
|
||
|
||
**This changes nothing today and is not a reason to requote D5.** At `0.2` both
|
||
forms are unambiguous, and neither consumer that reads the key parses YAML:
|
||
commons is line-oriented (`method-spec.md:90`) and catalog's gate unquotes before
|
||
a text-shape regex (`1ca27f6`). The exposure is latent and bounded — it opens at a
|
||
two-digit minor or a three-segment version. It is recorded here because
|
||
`okf_version`'s **value** belongs to catalog (decision E1), which makes this a
|
||
constraint we owe them before such a version exists rather than a choice we may
|
||
make for them.
|
||
|
||
**A green TEST B does not confirm the placement, and a red one does not
|
||
identify it.** The reader is placement-blind: green measures the value, never
|
||
the position. What changed on 2026-07-31 is only the REASON — it was an
|
||
unanchored regex matching a frontmatter line and a body line alike; from 0.3
|
||
it is a deliberate choice, reading frontmatter first and falling back to the
|
||
body, reporting which form it found. Catalog's stated ground for not enforcing
|
||
placement: `check-okf-parity` runs okr's LIVE checker, and enforcing before the
|
||
emitters have migrated would redden that gate without making a single bundle
|
||
more conformant. Read TEST B as a statement about the value's shape only, in
|
||
both directions. A red run still cannot separate "wrong quoting" from "wrong
|
||
placement" — but quoting is now the *less* likely of the two, since both forms
|
||
pass.
|
||
|
||
**TEST B must record the reader revision.** `okf-check.mjs` changed in
|
||
`1ca27f6`, and catalog invoked their standing promise to notify on any change
|
||
to it. A TEST B report that does not name the revision it ran is not
|
||
reproducible, whatever it says about the result.
|
||
|
||
**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.
|
||
|
||
### The ingest stamp's byte form is the ownership contract — decided 2026-08-02
|
||
|
||
Door A's `generated` value is `{ by: process:okf-ingest, at: <ingested_at> }`, and
|
||
`OwnershipPolicy.owns` recognises it with a string-prefix test. A consumer wanting
|
||
to answer "did this library write this file?" for itself has two things it could
|
||
bind to: those bytes, or that function. po-claude raised the first as a de facto
|
||
interface (2026-07-31); we recorded it as a decision that must fall before GA
|
||
rather than as something we were considering.
|
||
|
||
**Decided (operator, 2026-08-02): the byte form is the intended contract, and the
|
||
guarantee is formalised at GA, not now.** Three facts settle it, and the third is
|
||
what makes the choice narrower than it looks:
|
||
|
||
- The actor value deliberately carries no version (V1(d)), so the prefix is stable
|
||
across releases by construction. That is what makes it promisable at all.
|
||
- Recognition is a string convention, not structure parsing. Measured on the
|
||
consumer side: a line-oriented parser returns the whole flow mapping as one
|
||
opaque string and the prefix test passes on it. Neither side needs a nesting
|
||
model to participate.
|
||
- **Phase 4's Node half shares the contract and the fixtures, never the code.** A
|
||
Node consumer can never call `OwnershipPolicy.owns`. Making the API the
|
||
load-bearing contract would leave the whole Node side with no ownership contract
|
||
at all, or drive it to reimplement the byte test unofficially — the same binding
|
||
without the promise.
|
||
|
||
`OwnershipPolicy.owns` is therefore the canonical *implementation* of that test for
|
||
Python consumers, not the contract itself.
|
||
|
||
**The guarantee waits for GA, on the reason we gave po-claude on 2026-07-31:** the
|
||
profile is provisional on a pre-release precisely so pilot feedback can move the
|
||
surface without a deprecation cycle, and the pilot's one outstanding signal (Test
|
||
A) blocks GA regardless. Deciding the policy now and shipping the guarantee at GA
|
||
honours "the decision falls before GA" without binding the emitter mid-pilot.
|
||
|
||
**What is already true, and what is not.** The byte form is asserted today in
|
||
`test_okf_v0_2_profile.py:51` and `test_profile_threading.py:91`, so it is locked
|
||
against unintended drift. That is an observation of our own emission, not a
|
||
promise — a test carrying no stated promise is one we may change together with the
|
||
test. A test asserting that the prefix *is* a public interface belongs at GA.
|
||
|
||
**Owed, and sent 2026-08-02.** We told po-claude they would hear when the decision
|
||
fell, before GA rather than with it. It fell on 2026-08-02 and the notice went out
|
||
the same day, carrying three things: the decision itself, the Node-half reason that
|
||
made it narrow, and the explicit limit that the guarantee is formalised at GA — so
|
||
they can plan the ownership test against the prefix without treating it as promised
|
||
yet. It also corrects the record on one point rather than only the outcome: our
|
||
2026-07-31 advice against binding to `OwnershipPolicy.owns` stands, but the reason
|
||
we gave then (it costs them a dependency) was not the load-bearing one.
|
||
|
||
### 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.
|
||
9. The stamp's byte-form guarantee is stated where a consumer actually reads it
|
||
(CHANGELOG and the v0.2 profile docstring) and pinned by a test that asserts it
|
||
as a public interface rather than as an observation — and po-claude has been
|
||
sent the notice owed since 2026-08-02. The notice is due *before* the GA
|
||
release, not as part of it: that is what we told them, so a criterion checked
|
||
at GA is the backstop here and not the mechanism. **The notice half is done —
|
||
sent 2026-08-02;** what remains at GA is the stated guarantee and its test.
|
||
|
||
**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.
|
||
|
||
**Settled for OUR profile 2026-07-27 — and only for ours. SUPERSEDED 2026-07-31:
|
||
the actor is `process:okf-ingest`, and option (d) is excluded.** Commons decided
|
||
the fixed id on our own proposal, and `ingest-spec.md:7-8` — frozen on the spec
|
||
being framework-neutral — is what excludes (d): our repo name in the normative
|
||
id would force every other conformant implementation to write it into its own
|
||
output. The paragraph below is kept as written because its *reasoning* still
|
||
holds and only its outcome moved; the ask it describes was discharged in the
|
||
direction we control, just not at the value we first chose.
|
||
|
||
The operator chose (d) for `OKF_V0_2`, which shipped in D2 as
|
||
`OwnershipPolicy(actor="process:llm-ingestion-okf")`. Keep the two apart: V1 as
|
||
raised is about `DEFAULT`'s `generated`, and `DEFAULT` states **commons'** §5
|
||
layer, so that half stays in their ratification queue as item 8 and is not
|
||
settled by this. What this does settle is that the ask above is discharged in the
|
||
direction we control — the v0.2 fixture will be frozen at D5 carrying the
|
||
constant actor, so there is no regeneration to do later. It also makes our
|
||
recommendation to commons and our own implementation agree, which is worth
|
||
something when they weigh it: we took the cost we were recommending they take.
|
||
|
||
**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 was that Door C should **refuse** such a concept, failing closed.
|
||
|
||
**Superseded by the operator 2026-07-31: import and report.** The leaning had a
|
||
problem measurement surfaced and the prose did not. §14 forbids a consumer to
|
||
reject a bundle over a broken cross-link, and the spec does not settle whether
|
||
`executor.resource` counts as one; §10.5, by contrast, explicitly asks a consumer
|
||
to *surface* rather than silently drop. Reporting honours the rule that is
|
||
unambiguous without testing the rule that is not, and it leaves this door's one
|
||
invariant — the sender's bytes, verbatim — untouched.
|
||
|
||
`ImportResult.unverified_references` is an advisory over the merged set rather
|
||
than a fifth bucket: every concept it names has already merged, and a refused
|
||
concept is never named, because nothing was written for an operator to inspect.
|
||
|
||
**The report names the pointer key, not the resource** — and that is a measured
|
||
limit, not a preference. Resolving the resource means reading `executor.resource`,
|
||
the one value the line-oriented parser cannot recover in *either* canonical form:
|
||
the block mapping flattens and collides, the flow mapping stays one opaque string.
|
||
A resource-level report would therefore be empty or wrong on exactly the forms
|
||
upstream writes, while key presence survives both. Precision arrives with D1b.
|
||
|
||
**Measured end-to-end against the real guard, 2026-08-02 (v0.3.4, resolved version
|
||
confirmed).** The D4 tests run through a stub gate, which cannot answer whether the
|
||
report ever fires in production. One concept per row, through Door C:
|
||
|
||
flat baseline, no pointer -> merged report: []
|
||
executor: { resource: … } (flow) -> rejected report: []
|
||
fail_secure: value begins with a disallowed YAML indicator '{'
|
||
attester:\n resource: … (block) -> rejected report: []
|
||
fail_secure: nested mappings are not supported in OKF frontmatter
|
||
attester:\n - resource: … (one-key seq) -> merged report: [attester]
|
||
|
||
Both *canonical* forms are stopped by the guard's grammar before the report can see
|
||
them. The only shape that carries a §10 pointer through Door C today is the
|
||
non-conformant one-key block sequence — precisely the hole the guard recorded as
|
||
theirs (their item 3, 2026-07-26). Two consequences: the ordering they recommended
|
||
holds, since our report pre-empts no grammar decision of theirs while it stays
|
||
empty against everything conformant; and if they fix their item 3, today's only
|
||
pointer path closes too. Neither is an argument for leaving it unfixed — an
|
||
advisory with nothing to report is the better outcome. Sent to them 2026-08-02.
|
||
|
||
Still theirs, and still open: whether a non-`.md` code file inside a bundle is
|
||
within `okf.import_bundle`'s mandate. Nothing above answers it — this decision
|
||
covers what *we* do with a pointer, not what the guard owes for the thing pointed
|
||
at, and the execution boundary named two paragraphs up still has no owner.
|
||
|
||
## 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. **Done 2026-07-27**
|
||
(`c90171d`, 542 tests). Ownership became an `OwnershipPolicy` on the profile
|
||
rather than a literal in the gate — the emitter and the predicate are coupled
|
||
through the stamp value, and a policy is what makes them changeable only
|
||
together. Recognition is one-way by decision (v0.2 owns the v0.1 stamp, not
|
||
the reverse), so V-A3's fail-safe survives. `okf_version` is deliberately NOT
|
||
declared here; it lands once, at D5, when the placement question closes.
|
||
Requirement 6 — the keyword-only `profile` parameter on `materialize_bundle`
|
||
— is next and is what makes the profile reachable from outside the library.
|
||
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`. **Done 2026-07-31** (559
|
||
tests): `examples/ingest-golden-okf-v0-2/`, emitted by the code and frozen
|
||
byte-exact, with the marker in the root `index.md`'s frontmatter block per
|
||
upstream §8/§12. The value reaches the emitter as
|
||
`materialize_bundle(..., root_frontmatter_values={"okf_version": ...})` —
|
||
keyword-only, defaulting to none, so every existing call site is untouched.
|
||
The profile names the key (`OKF_V0_2.index.root_frontmatter`) and never
|
||
carries the value, which is V4/V-A5 held intact: the value is catalog's (E1),
|
||
and in the fixture it is fixture DATA (`okf-version.txt`), not a constant in
|
||
our source. Offering a key the policy does not name is refused fail-fast
|
||
before any disk mutation.
|
||
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.
|
||
**Done 2026-07-31**: 17 bundles, 602 documents, five corpora (the pilot set
|
||
plus upstream's own reference bundles), six findings — all on the judge path,
|
||
none blocking the tag. Results and the Test C verdict are under "P1 results"
|
||
above; the wiki's copy went out through coord.
|
||
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.
|
||
**Done 2026-07-31** — format only, and taken ahead of steps 9–10 because the
|
||
pilot's two closed tests left D4 the one piece with no dependency on
|
||
feedback still outstanding. Details under D4 above.
|
||
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. **DONE 2026-07-31 @ `3fcbb9f`: 13/13.** Procedure is runbook Step 3a |
|
||
|
||
V-A8 is the one that keeps this honest. Every other test asks whether we agree
|
||
with ourselves.
|
||
|
||
**Executed 2026-07-31, and it was nearly weakened on a false premise.** The
|
||
standing note said V-A8 had to be re-scoped to parse-and-render because "there is
|
||
no validator in `okf/`". Enumerating the tree instead of the subdirectory showed
|
||
`OKFDocument.validate()` at `okf/src/reference_agent/bundle/document.py:58`, inside
|
||
a working v0.2 reader. The claim was a negative derived from a partial enumeration,
|
||
which is the failure mode runbook Step 2 now names explicitly.
|
||
|
||
The test ran unweakened. `document.py` imports only `yaml`, so upstream's reader
|
||
runs standalone against the pinned clone with nothing installed and `.venv`
|
||
untouched. **13/13 green** over `examples/ingest-golden-okf-v0-2/`:
|
||
|
||
- Both files parse under a real `yaml.safe_load`.
|
||
- `generated` arrives as a **mapping** — `{'by': 'process:okf-ingest', 'at':
|
||
datetime(2026, 7, 16, 12, 0, tzinfo=utc)}` — and `sources` as a **list of
|
||
mappings** carrying `id` and `resource`. This is the assertion no test of ours
|
||
could make: our parser is line-oriented and reads both as opaque strings.
|
||
- `validate()` passes, `trust_tier` → `unverified`, `normalize_verified` → `[]`,
|
||
`is_stale` → `False`, and `serialize()` → `parse()` preserves frontmatter
|
||
semantics and body bytes.
|
||
|
||
Two measured facts worth carrying, neither of them failures:
|
||
|
||
1. **`generated.at` type-coerces to `datetime`** under a real YAML parser while our
|
||
parser keeps the string. Same one-way asymmetry po-claude reported from the
|
||
other side on 2026-07-31 (their line-oriented parser sees the whole mapping as
|
||
one opaque string). Neither is wrong; ownership recognition
|
||
(`OwnershipPolicy.owns`) is a string prefix test and is unaffected by either.
|
||
2. **`serialize()` reflows our inline flow forms to block form.** An upstream
|
||
round-trip therefore yields bytes our own parser cannot read — the exact
|
||
pollution `test_a_block_list_pollutes_the_scalar_parsers_key_space`
|
||
characterizes. Expected, one-directional, and not a reason to emit block form.
|
||
|
||
**V-A8 cannot become a pytest test**, and that is not a gap: it needs PyYAML and
|
||
upstream's source, and this package has exactly one runtime dependency with a
|
||
packaging test enforcing it. It is a per-release runbook procedure (Step 3a), which
|
||
is also where an upstream reader change would be caught.
|
||
|
||
## 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.
|
||
**Done 2026-07-31 @ `3fcbb9f`, 13/13.** Re-run per upstream release via runbook
|
||
Step 3a — it is a procedure, not a pytest test, because it needs PyYAML and
|
||
upstream's source and this package has exactly one runtime dependency.
|
||
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.
|