Operator GO 2026-08-23. Two findings outrank the change that triggered the round. OKF moved to GoogleCloudPlatform/open-knowledge-format (6265173). The tree this repo pinned, knowledge-catalog path okf/, is now a frozen snapshot by upstream's own notice, and the runbook's Trigger step named it. The two trees have already diverged: the frozen copy carries a fix (38c713f, eight tags: values as sequences rather than one plain scalar) that the canonical repo does not. The spec tightened without a version bump. SPEC.md still declares Version 0.2, but every timestamp-valued key is now an ISO 8601 datetime with an explicit offset, so a value that conformed in July does not conform now under the same version number. Step 4 gained a fifth diff class for it. Our exposure is zero, measured: 10 of 10 timestamp values across the four goldens already carry a Z offset, gated by _INGESTED_AT_RE. The three tightened keys (stale_after, last_modified, usage_window) are named but never written by any door. V-A8 at the new pin: 9/9 of our bundle documents parse, 78/78 of upstream's do. Two reference-reader behaviour changes recorded. A date-only stale_after now reads as never stale rather than as stale -- silent, fails open, and the class the black box cannot absorb. Timestamps now survive a parse as written, so our ingested_at arrives as str where it used to arrive as datetime. No new profile: there is no new version to express. The pin move and consumer notification are named as deliverables and deliberately not taken here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013j8ZEcjiZQpz22fecNsyLZ
18 KiB
Upstream OKF round, 2026-08-23 — the timestamp tightening, and the repository move
Runbook run of docs/upstream-okf-upgrade-runbook.md, executed 2026-08-23 on an
operator GO. Previous round: docs/plan/okf-v0.2-alignment.md (v0.1 → v0.2, pinned
3fcbb9f).
This round found two things the runbook was not shaped for, and both matter more than the change that triggered it:
- OKF moved to its own repository. The tree we had pinned is now a declared frozen snapshot. The runbook's Trigger step pointed at it by name.
- The spec changed without a version bump.
SPEC.mdstill says Version 0.2. Step 4's classification vocabulary had no bucket for that.
The triggering change itself — every timestamp becomes an ISO 8601 datetime with an explicit offset — costs this library nothing. That conclusion is measured, not assumed; the denominators are below.
Step 1 — Pins
Everything below was read at a fixed commit, never at a branch.
| What | Commit | Date | Role |
|---|---|---|---|
GoogleCloudPlatform/open-knowledge-format |
ad30107c31c06aec8a7d5636e0d1058118604e6f |
2026-08-21 | New canonical source. Read at this pin |
GoogleCloudPlatform/knowledge-catalog |
395a0c311979858dcecfed767810658baf3ca3f3 |
2026-08-22 | Frozen okf/ snapshot, read for divergence |
GoogleCloudPlatform/knowledge-catalog |
3fcbb9f828c2f23d109c855ee403c3a4c81f3a96 |
2026-07-24 | Our current pin, the v0.2-round baseline |
The order that authorized this run named 62432a09 as the target. That commit is
real (okf: make every timestamp an ISO 8601 datetime with an explicit offset (#323), 2026-08-20) but it is a commit in the frozen tree, and it was not the
head of anything by the time this ran. Verified before use rather than adopted:
knowledge-catalog had advanced 53 commits past our pin, and the canonical tree is
a different repository.
Local read-only mirrors: ~/repos/_okf-upstream (knowledge-catalog, pre-existing)
and ~/repos/_okf-canonical (new, cloned this round, push URL disabled).
Step 2 — Enumeration, with denominators
Canonical repo at ad30107: 132 tracked files.
| Top level | Files |
|---|---|
bundles/ |
83 (acme_retail 19, crypto_bitcoin 16, ga4 15, stackoverflow 33) |
src/ |
27 |
tests/ |
8 |
samples/ |
6 |
connectors/ |
1 — new, not present in the tree we had pinned |
| root files | 7 (SPEC.md, README.md, pyproject.toml, LICENSE.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, .gitignore) |
By extension: 89 md, 31 py, 5 html, 3 txt, 1 each of toml, js, css,
gitignore.
Of the 53 knowledge-catalog commits between our pin and its current head, 3
touched okf/. The other 50 are toolbox/ work (mdcode, enrichment) and are
out of scope for this library — stated with the denominator, not as an impression.
Step 3 — The move, read from the primary source
6265173 okf: point readers at the dedicated OKF repository (#324) adds a notice to
okf/README.md:
OKF now lives in its own repository: GoogleCloudPlatform/open-knowledge-format. … Stop using the copy under
okf/in this repository. It is a frozen snapshot, no longer maintained, and anything built against it will drift out of date.
This is the finding with the longest reach. "Always latest" is a promise about a
tree, and the tree changed identity. The runbook's Trigger step — Check
GoogleCloudPlatform/knowledge-catalog, path okf/ — would have kept sending
future rounds at a snapshot that upstream says will drift, and each such round would
have reported "no change" truthfully and uselessly.
The two trees have already diverged
The canonical repo does not carry 38c713f okf: write stackoverflow bundle tags as YAML lists (#293) (2026-08-14), which is present in the snapshot it declares
obsolete. Measured across the 8 affected files, each differing by exactly the tags:
line and nothing else:
canonical: tags: stackoverflow, posts, questions # one plain scalar
frozen kc: tags: [stackoverflow, posts, questions] # a sequence
Upstream's own commit message for that fix records what the scalar form cost them: a JavaScript string is iterable, so each one expanded into one label per character. The canonical repository currently ships the form upstream had already diagnosed as corrupting.
We are not exposed: this library neither writes nor reads OKF tags (measured — one
occurrence of the substring across 11 modules, in extract.py:86, about HTML tags).
Door C is exposed only in the sense that it merges external concepts verbatim by
design, so a consumer importing the canonical stackoverflow bundle would carry the
scalar form through unchanged. That is the verbatim invariant working, not a defect.
Step 4 — The diff, classified
Between our pin 3fcbb9f:okf/ (129 files) and canonical ad30107 (132 files), every
.py file differs by a 14-line Apache licence header added at the move. Confirmed as
an artifact rather than assumed: stripping those 14 lines makes the files byte-
identical. Excluding it, the substantive set is small.
Normative (SPEC.md), 22 changed lines, all one change. Every timestamp-valued
key becomes an ISO 8601 datetime with an explicit UTC offset. New sentence in §5:
Every timestamp-valued key in OKF is an ISO 8601 datetime with an explicit UTC offset, for example
2026-06-30T14:00:00Z.
Three keys change form; the rest of the diff is the examples and prose following them:
| Key | Was | Is |
|---|---|---|
stale_after (§5.5) |
YYYY-MM-DD |
datetime with offset |
sources[].last_modified (§5.1) |
YYYY-MM-DD |
datetime with offset |
usage_window.{from,to} (§5.1) |
date range | datetime range |
generated.at and verified.at were already datetimes with Z and are untouched.
Not a version bump. SPEC.md still declares Version 0.2, and §12's minor/major
rules are unchanged. This is a tightening of the normative text in place: a value
that conformed on 2026-07-24 does not conform now, under the same version number. The
runbook's Step 4 vocabulary (Breaking / Additive / Deferred upstream / Observed but
undocumented) has no bucket for it, which is why it is named here as a fifth class —
tightened in place, no version bump — and why declaring conformance to "v0.2"
without also naming a commit is now ambiguous. Our okf_spec_commit key already
anticipated this; this round is the first time it earns its keep.
Reference reader (src/reference_agent/bundle/document.py), 34 substantive
lines, two behaviour changes.
Change 1 — a date-only stale_after is now silently ignored. is_stale returns
False for any value without a T, on the stated reasoning that a date-only value
"names a different instant in every timezone, so it is ignored rather than guessed
at". Measured on identical input at both pins, with now set 17 months past the
date:
stale_after |
reader @ 3fcbb9f |
reader @ ad30107 |
|---|---|---|
2026-12-31 |
is_stale=True |
is_stale=False |
2026-12-31T00:00:00Z |
True |
True |
2026-12-31T00:00:00+00:00 |
— | True |
This is the class the black-box promise explicitly cannot absorb. No library change
makes an already-authored date-only stale_after mean what its author meant; it now
reads as never stale rather than as stale, which fails open. Nothing in this
library emits stale_after, so our first-order exposure is zero — but any consumer
who authored one has content whose meaning reversed without their file changing.
Change 2 — timestamps now survive a parse as the text the author wrote. Upstream
subclasses SafeLoader and drops the tag:yaml.org,2002:timestamp implicit
resolver, because PyYAML implements YAML 1.1 and was rewriting frontmatter on a
round-trip. Measured against our own golden, same bytes, both pins:
| Key | type @ 3fcbb9f |
type @ ad30107 |
|---|---|---|
ingested_at |
datetime |
str ('2026-07-16T12:00:00Z') |
generated.at |
datetime |
str |
okf_version |
float (0.2) |
float (0.2) — unchanged |
This is a change in our favour: the value an upstream consumer recovers from our
bundle is now the literal we wrote. It also narrows the A-E6 finding — the float
coercion on okf_version survives, so that one is still real and still upstream's.
Additive: connectors/gcp-knowledge-catalog.md (new file), CONTRIBUTING.md and
CODE_OF_CONDUCT.md (repository furniture that came with the move).
Deferred upstream (unchanged): §12 still defers the receipt and verdict wire
formats, the attester ABI, attestation caching, and semantic-layer templates. Our
Attested Computation support stays format-only on upstream's deferral, not on our
preference.
Observed but undocumented: none this round.
Step 3a — V-A8, upstream's reader against our fixtures
Run with /usr/local/bin/python3 (3.14.0, PyYAML 6.0.3) against the pinned clone.
The .venv was not touched and nothing was installed; the one-runtime-dependency
rule and its packaging test are intact.
| Bundle | .md files |
parsed | parse failures |
|---|---|---|---|
our ingest-golden-file |
3 | 3 | 0 |
our ingest-golden-http |
2 | 2 | 0 |
our ingest-golden-okf-v0-2 |
2 | 2 | 0 |
our ingest-golden-sql |
2 | 2 | 0 |
upstream acme_retail |
17 | 17 | 0 |
upstream crypto_bitcoin |
15 | 15 | 0 |
upstream ga4 |
14 | 14 | 0 |
upstream stackoverflow |
32 | 32 | 0 |
9 of 9 of our own bundle documents parse under the new reader, and
trust_tier, normalize_verified and is_stale run over each without raising.
A clean sweep is a weak result unless the harness is shown to be able to fail, so it
was put to a known-positive: a document with type: [unclosed raises
OKFDocumentError as expected, while a well-formed control parses. The zeroes above
are measurements, not the absence of one.
Two notes on what V-A8 does not buy, both carried forward from the v0.2 round:
validate()checksREQUIRED_FRONTMATTER_KEYS = ("type",)— one key. It fails on our rootindex.md, correctly: an index is not a concept and carries notypeby §12. The runbook's recorded "13/13" counted concept documents; this run's harness assertedvalidate()on index files too and so reports one expected failure per bundle root. Not a regression — a stricter harness. Do not read the two counts as comparable.- Upstream's
serialize()still reflows inline flow forms to block form. Unchanged, one-directional, and still not a reason to emit block form (D4).
Step 5 — Exposure
Ours: measured zero
Every timestamp-valued byte this library emits, across all four golden bundles — denominator 10 of 10, every one already conformant:
examples/ingest-golden-file/expected-bundle/ingest-orders.md:5 ingested_at: 2026-07-16T12:00:00Z
examples/ingest-golden-file/expected-bundle/ingest-orders.md:7 generated: { by: process:okf-ingest, at: 2026-07-16T12:00:00Z }
... 4 further bundles, same two keys, same form
The gate is materialize.py:37,
_INGESTED_AT_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$"), enforced by
validate_ingested_at (materialize.py:49) and called on every Door A run
(materialize.py:386). generated.at derives from the same validated value via
profile.ownership.stamp(ingested_at) (materialize.py:235), so both keys are
covered by one check. The spec's own example of an explicit UTC offset is Z, so
this form conforms as written.
The three keys the tightening actually changes are not written by any door:
stale_afteris named but not written —profiles.py:757places it in_OKF_V0_2_KEY_ORDERso a caller can emit it in canonical order and the schema can judge a document carrying one. The comment atprofiles.py:736says so directly: "Naming a family is not writing it … Door A writes none of them."last_modified,usage_window,usage_countare not emitted at all.materialize.py:188records the decision: a manifest source has noauthorand nolast_modified, "and inventing them would be writing fields with no reader."
Nothing in this library validates a timestamp value other than ingested_at
(measured across 11 modules; the query was validated against a known-positive control
before its empty result was believed).
One test fixture carries a now-non-conformant literal:
tests/test_okf_v0_2_characterization.py:188 uses stale_after: 2027-01-01 inside a
§14 consumer-tolerance case — a document a conformant consumer MUST NOT reject.
That is still exactly what it tests, and the date-only form arguably tests it better
now. No change proposed. Flagged so a later reader does not "fix" it.
Consumers: framed for them to measure, not decided for them
The list was re-derived against disk rather than trusted from the runbook, and all eight are present (two were initially recorded as absent from a guessed path — a wrong-path negative, corrected before use):
portfolio-optimiser-commons, portfolio-optimiser, portfolio-optimiser-claude,
claude-code-llm-wiki, catalog, okr, linkedin-studio, ms-ai-architect.
What has to be true for the tightening to reach a consumer — one falsifiable question each, for them to run against their real data:
- Do you author
stale_after,last_modified, orusage_windowanywhere? If no, your exposure is zero and this round costs you nothing. If yes, and the value is date-only, an OKF v0.2 reader at the current spec now reads it as never stale rather than as stale. It fails open, and it is silent. - Do you declare conformance as "v0.2" without naming a commit? That string no longer identifies one document. Naming the commit alongside it is the fix.
- Do you read OKF from
GoogleCloudPlatform/knowledge-catalog, pathokf/? That copy is frozen by upstream's own notice, and has already diverged.
Nothing here asserts which consumers are affected. The v0.2 round showed the falsifiable framing is what made the answers useful.
Step 6 — The plan
No new profile. Support is additive by rule, but there is nothing additive to
express: the spec did not bump, the three tightened keys are ones we do not write,
and what we do write already conforms. Inventing an OKF_V0_2_1 profile would
declare a version upstream has not minted. Conform first, claim after.
Deliverables, in order:
- Correct the runbook's Trigger step to name
GoogleCloudPlatform/open-knowledge-formatas canonical, record thatknowledge-catalog/okf/is frozen and already divergent, and add the fifth diff class (tightened in place, no version bump) to Step 4. Done this round — leaving a known-false instruction in the procedure would send the next run at a dead tree. - Move the pin
3fcbb9f→ad30107, and with it the repository. Cost, measured: zero bytes in our fixtures.okf_spec_commitis a key theSTRICT_V1profile requires (profiles.py:721,726); its value is the caller's under D5, and no shipped fixture carries one. The cost lands where D5 says it should — on the caller that stamps it, as a re-run. This is a separate decision and is not taken here, per the order. - Re-record V-A8 at the new pin in
docs/plan/okf-v0.2-alignment.md's verification table, which currently reads "DONE 2026-07-31 @3fcbb9f: 13/13". Pending the pin decision, so the two move together. - Notify consumers (Step 7) with the three questions above. Not sent this round — the order scoped this to analysis and a plan document, and notification is a sent action.
No pilot, no GA event. Pilot-first governs a new upstream version reaching a
new profile. There is no new version and no new profile, so OKF_LATEST does not
move and there is nothing to flip. Saying this explicitly because the standing
policy's machinery is easy to run out of habit.
Guard seam: nothing in this round touches it. The tightening is a value-format question inside frontmatter we already gate; the persist gates are unchanged, and the standing posture that everything entering a bundle passes the guard is unaffected. Noted, not built.
Step 8 — Verification log
Every claim above and the command class that produced it. Corrections stay visible.
| Claim | How verified |
|---|---|
OKF moved; okf/ is frozen |
git show 6265173 — primary source, upstream's own notice text |
Canonical pin ad30107, 132 files |
git log -1, git ls-files | wc -l at the pin |
3 of 53 kc commits touched okf/ |
git log --oneline 3fcbb9f..origin/main -- okf |
.py diffs are a licence header |
Stripped 14 lines, files byte-identical (diff empty) |
| Spec change is timestamps only | diff -u of the two SPEC.md files, whole diff read |
| Still Version 0.2, no bump | SPEC.md:3, §12 read in full at the new pin |
Canonical lacks 38c713f |
diff -rq frozen-vs-canonical; 8 files, each 2 lines, all tags: |
We never write tags |
grep across 11 modules — 1 unrelated hit (extract.py:86) |
is_stale reversal |
Same input run against both pinned readers, now=2027-06-01 |
ingested_at type change |
V-A8 type dump, same golden bytes, both pins |
| Our exposure is 10/10 conformant | Frontmatter scan over all 10 examples/**/*.md |
| No other timestamp validation | grep over 11 modules, validated against a known-positive control first |
okf_spec_commit has no fixture value |
grep examples/ exit 1, after git grep confirmed the key exists in 3 tracked places |
| V-A8 harness can detect failure | Deliberate type: [unclosed → OKFDocumentError; control parses |
| All 8 consumers present | find ~/repos -maxdepth 3 -type d -name <n>, with a known-positive control |
| Suite green | .venv/bin/python -m pytest -q → 596 passed |
Corrections made during this run, kept visible:
- The order named
62432a09as the target. It is a commit in the frozen tree, not the canonical head. Verified before it was used as a premise. linkedin-studioandms-ai-architectwere first recorded as absent, from a guessed path (~/repos/<name>). A wider search found both under~/repos/ktg-plugin-marketplace/. A negative from a guessed path is not a measurement.- An early
grep … | head; echo $?reported the exit status ofhead, not ofgrep— a negative that measured nothing. Re-run with the status of the actual query before its empty result was used.