# OKF v0.2 frontmatter: what the spec requires, and two ways to check a folder

> Source: <https://dev.to/oocm147/okf-v02-frontmatter-what-the-spec-requires-and-two-ways-to-check-a-folder-1b2o>
> Published: 2026-10-22 01:24:23+00:00

*I make JidoSeal. Free and local; the scan engine is proprietary; JidoSeal also sells optional certificates.*

If you keep notes, docs or a knowledge base as markdown with YAML frontmatter, and you point an AI tool at them (Claude Code, Cursor, Copilot, Codex, Gemini CLI, Zed, a local model), Google Cloud's Open Knowledge Format (OKF) is worth ten minutes. It is a small, open spec for exactly that kind of folder: "a directory of markdown files with YAML frontmatter", meant to be "authored by people, generated by agents... and consumed by both". This post covers what v0.2 actually requires, which is less than most people assume, and then two ways to check a folder against it.

The spec is short. You can read all of it at [https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md](https://github.com/GoogleCloudPlatform/open-knowledge-format/blob/main/SPEC.md). Here is the part that matters for frontmatter.

**A bundle is just a folder.** Every `.md` file in it is a "concept document", except two reserved filenames: `index.md` (a directory listing) and `log.md` (an update history). Subfolders are fine, and git is the recommended way to ship it.

**One field is required: `type`.** Section 4.1 lists it as the only required key, and says "a concept carrying just `type` is fully conformant". Its values are free text that you choose (`policy`, `procedure`, `Metric`, `Playbook`...). Nobody registers them centrally, and consumers must handle types they don't recognise.

So this file is valid OKF v0.2:

```
---
type: policy
---

# Leave policy

Employees accrue leave monthly.
```

**Four fields are recommended:** `title`, `description` (one sentence), `resource` (a URI for the thing the note describes, if there is one), and `tags` (a YAML list). If `title` is missing, a consumer may derive it from the filename.

**Optional families, if you want more:**

`generated: { by, at }` records who or what wrote the content. `verified` is a list of `{ by, at }` entries. A consumer derives a trust tier from it: no `by: human:<id>` means human-reviewed.`status` is `draft`, `stable` or `deprecated`, and defaults to `stale_after` is an absolute instant after which the note counts as stale.`sources` is a list of entries, each with a required `resource`.
**Conformance (section 11) is three rules.** Every non-reserved `.md` file has a parseable YAML frontmatter block. Every block has a non-empty `type`. `index.md` and `log.md` follow their own sections if present. Just as useful is what consumers **must not** reject a bundle for: missing optional fields, unknown `type` values, unknown extra keys, broken cross-links, or missing `index.md` files. Extra keys of your own are fine.

That gives you a concrete thing to check: does every note open with a frontmatter block, and does that block have a non-empty `type`? Then, softer: which of the four recommended fields are missing?

This reads only top-level `key: value` lines. That's enough for `type`, which is a plain string. It skips the reserved filenames, fails any file without a frontmatter block or a non-empty `type`, and lists the recommended fields that are missing.

``` python
import sys, pathlib
RECOMMENDED = ["title", "description", "resource", "tags"]
bad = 0
for p in sorted(pathlib.Path(sys.argv[1]).rglob("*.md")):
    if p.name in ("index.md", "log.md"): continue          # reserved filenames, §3.1
    lines = p.read_text(encoding="utf-8").splitlines()
    if not lines or lines[0] != "---" or "---" not in lines[1:]:
        print(f"FAIL {p}: no frontmatter block"); bad += 1; continue
    fm = lines[1:lines.index("---", 1)]
    keys = {l.split(":", 1)[0].strip(): l.split(":", 1)[1].strip() for l in fm if ":" in l and not l.startswith((" ", "#", "-"))}
    if not keys.get("type"):
        print(f"FAIL {p}: no non-empty 'type'"); bad += 1; continue
    missing = [k for k in RECOMMENDED if k not in keys]
    print(f"ok   {p}" + (f"  (recommended, missing: {', '.join(missing)})" if missing else ""))
sys.exit(1 if bad else 0)
```

Here it is on a sample folder of six notes, one of which has no frontmatter at all:

``` bash
$ python3 okf_check.py tiers
ok   tiers/bronze/leave-policy.md  (recommended, missing: title, description, resource, tags)
ok   tiers/gold/leave-policy.md  (recommended, missing: resource, tags)
ok   tiers/mixed/expenses.md  (recommended, missing: description, resource, tags)
ok   tiers/mixed/leave-policy.md  (recommended, missing: resource, tags)
FAIL tiers/mixed/notes.md: no frontmatter block
ok   tiers/silver/leave-policy.md  (recommended, missing: resource, tags)
$ echo $?
1
```

It exits with 1 when anything fails, so it can go straight into CI or a pre-commit hook. It is honest about its limits. It does not parse YAML properly, so it won't notice a frontmatter block that is broken YAML, and it would accept `type: ""`. Section 11 asks for "parseable" YAML, so if you need that guarantee, swap the `keys` line for `yaml.safe_load` and accept one dependency.

To list the notes with no `type` line at all:

``` bash
$ grep -rL --include='*.md' -E '^type: *[^ ]' tiers
tiers/mixed/notes.md
```

That is fast, but it also matches a `type:` line in the body of a note. For a real YAML read of the frontmatter only, mikefarah's `yq` (v4) has a front-matter mode:

``` bash
$ for f in $(find tiers -name '*.md' ! -name index.md ! -name log.md | sort); do printf '%s\t%s\n' "$f" "$(yq --front-matter=extract '.type // "MISSING"' "$f")"; done
tiers/bronze/leave-policy.md    policy
tiers/gold/leave-policy.md  policy
tiers/mixed/expenses.md procedure
tiers/mixed/leave-policy.md policy
tiers/mixed/notes.md    MISSING
tiers/silver/leave-policy.md    policy
```

Change `.type` to `.description` (or any other field) to find the notes that are missing that one.

If you'd rather have your AI tool do the check, I also maintain a free, local MCP server that runs it and lists each file's missing fields: [https://github.com/Jidoseal/jidoseal-mcp](https://github.com/Jidoseal/jidoseal-mcp)
