llm-ingestion-okf/docs/plan/okf-2026-08-timestamp-tightening.md
Kjell Tore Guttormsen e286b5a173 docs(upstream): move the OKF pin to canonical ad30107, re-record V-A8
The operator authorized the move from `3fcbb9f` (frozen `knowledge-catalog/okf/`)
to `ad30107` in `GoogleCloudPlatform/open-knowledge-format`. Cost to our fixtures
was measured at zero bytes before the decision and is unchanged: `okf_spec_commit`
is a key `STRICT_V1` names, its value is the caller's under D5, and no shipped
fixture carries one.

The old hash stays in the alignment plan rather than being rewritten. Every fact
under that heading was *read* at `3fcbb9f`; replacing the hash would give those
facts a provenance they do not have. What moves is the tree we measure against.

V-A8 re-run at the new pin: 9/9 of our golden documents parse under upstream's
reader, 0 failures, 27/27 semantic-reader calls (`trust_tier`,
`normalize_verified`, `is_stale`) return without raising. The zeroes are
measurements — the harness was put to a known-positive in both directions first:
`type: [unclosed` raises `OKFDocumentError` while a well-formed control parses,
and `is_stale` returns True for a past `stale_after` and False for a future one.
Types recovered: `ingested_at` -> str (was datetime), `generated` -> dict,
`sources` -> list, `okf_version` -> float (A-E6, still upstream's).

The `38c713f` divergence is carried forward as a written known divergence rather
than left to evaporate with the pin, and it is now recorded at pin-level
precision. `38c713f` is not an ancestor of `3fcbb9f` (`merge-base
--is-ancestor` exits 1) — it landed three weeks forward of the pin we left, so
both the old and the new pin carry the bare-scalar `tags:` form and the move
loses nothing. The accurate statement is that canonical ships a form its own
frozen predecessor has already repaired.

The open question is closed rather than deferred, because it was cheap: across
129 tracked text files present in both trees, 9 carry frozen-only lines - 8 are
`38c713f` and the 9th is the freeze notice in `README.md`. Zero files are
frozen-only. The divergence is fully enumerated.

Runbook Step 3a gains the harness trap that cost this round a re-run: the
semantic readers take the frontmatter mapping, not the document, and passing the
document raises an AttributeError that reads like an upstream incompatibility.
2026-08-23 20:30:22 +02:00

22 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:

  1. 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.
  2. The spec changed without a version bump. SPEC.md still 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, and our pin since 2026-08-23
GoogleCloudPlatform/knowledge-catalog 395a0c311979858dcecfed767810658baf3ca3f3 2026-08-22 Frozen okf/ snapshot, read for divergence
GoogleCloudPlatform/knowledge-catalog 3fcbb9f828c2f23d109c855ee403c3a4c81f3a96 2026-07-24 Our pin until 2026-08-23, 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() checks REQUIRED_FRONTMATTER_KEYS = ("type",) — one key. It fails on our root index.md, correctly: an index is not a concept and carries no type by §12. The runbook's recorded "13/13" counted concept documents; this run's harness asserted validate() 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_after is named but not writtenprofiles.py:757 places it in _OKF_V0_2_KEY_ORDER so a caller can emit it in canonical order and the schema can judge a document carrying one. The comment at profiles.py:736 says so directly: "Naming a family is not writing it … Door A writes none of them."
  • last_modified, usage_window, usage_count are not emitted at all. materialize.py:188 records the decision: a manifest source has no author and no last_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:

  1. Do you author stale_after, last_modified, or usage_window anywhere? 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.
  2. 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.
  3. Do you read OKF from GoogleCloudPlatform/knowledge-catalog, path okf/? 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:

  1. Correct the runbook's Trigger step to name GoogleCloudPlatform/open-knowledge-format as canonical, record that knowledge-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.
  2. Move the pin 3fcbb9fad30107, and with it the repository. Cost, measured: zero bytes in our fixtures. okf_spec_commit is a key the STRICT_V1 profile 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. DONE 2026-08-23 on the operator's decision; recorded in docs/plan/okf-v0.2-alignment.md § Provenance of the facts below.
  3. Re-record V-A8 at the new pin in docs/plan/okf-v0.2-alignment.md's verification table. DONE 2026-08-23 @ ad30107: 9/9 documents parse across all four goldens, 0 failures, 27/27 semantic-reader calls clean, with the harness put to a known-positive first in both directions (malformed → OKFDocumentError; is_staleTrue on a past stale_after).
  4. 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.

Known divergence, carried forward past the pin move

The pin moved on 2026-08-23. This section exists so the divergence recorded in Step 3 does not move with it, silently, and become a premise someone inherits in three weeks.

The divergence, at pin-level precision. Step 3 measured it between the frozen repository's head (395a0c3, 2026-08-23) and canonical ad30107, and that measurement stands. What it did not say, and what matters when the pin actually moves, is where our old pin sits:

Tree tags: form, the eight stackoverflow files
our old pin 3fcbb9f (2026-07-24) tags: stackoverflow, users, community, reputation — bare scalar
our new pin ad30107 (2026-08-21, canonical) tags: stackoverflow, users, community, reputation — bare scalar
frozen repo head 395a0c3 (2026-08-23) tags: [stackoverflow, users, community, reputation] — flow sequence

38c713f is dated 2026-08-14 and is not an ancestor of 3fcbb9f (git merge-base --is-ancestor → exit 1); it landed three weeks forward of the pin we are leaving. So the pin move loses nothing — neither pin ever had the fix. The correct statement is not "moving the pin drops 38c713f" but: a fix upstream has already written is unreachable from the canonical tree, and the move does not change that. Canonical ships the form its own frozen predecessor has repaired.

Writing it the loose way — "the frozen tree carries it, canonical lacks it" — reads as content lost by moving, and would send a later session looking for a regression the move did not cause.

Is 38c713f the only content that exists only in the frozen tree? Yes. The question was cheap, so it was answered rather than deferred. Denominator: 129 tracked text files present in both 395a0c3:okf/ and ad30107, compared as line multisets. Nine files carry at least one frozen-only line: eight are exactly 38c713f's tags: lines, and the ninth is README.md, whose eleven frozen-only lines are upstream's own freeze notice — which by definition can only exist in the frozen repository. Zero files exist only in the frozen tree; canonical adds three (CONTRIBUTING.md, CODE_OF_CONDUCT.md, connectors/). Nothing else is frozen-only, so this divergence is fully enumerated and does not need re-deriving next round.

Our exposure: zero, and unchanged by the move. This library never writes tags (grep across 11 modules; one unrelated hit at extract.py:86, about HTML tags). Door C merges external concepts verbatim by design, so a consumer importing the canonical stackoverflow bundle carries the scalar form through unchanged — the verbatim invariant working, not a defect.

Related, not the same finding: G33 records that tags and description block bundle import universally at the guard, measured by llm-ingestion-pipeline-security. That is a guard-grammar question on their side of the seam. Noted here only so a later reader does not merge two tags findings that share a key name and nothing else.

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: [unclosedOKFDocumentError; 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
38c713f not an ancestor of 3fcbb9f git merge-base --is-ancestor 38c713f 3fcbb9f → exit 1
Both pins carry the bare-scalar tags: grep -m1 '^tags:' on the same 8 files in both git archive exports
38c713f is the only frozen-only content Line-multiset difference over 129 files present in both trees: 9 files, 8 = tags:, 1 = the freeze notice in README.md; query validated by the 65/65 tags: control
V-A8 re-run @ ad30107 9/9 parsed, 27/27 semantic calls clean; harness shown able to fail (type: [unclosedOKFDocumentError) and able to report a positive (is_staleTrue on a past stale_after)

Corrections made during this run, kept visible:

  • The order named 62432a09 as the target. It is a commit in the frozen tree, not the canonical head. Verified before it was used as a premise.
  • linkedin-studio and ms-ai-architect were 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 of head, not of grep — a negative that measured nothing. Re-run with the status of the actual query before its empty result was used.