okf 1.13.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +195 -0
- data/README.md +25 -6
- data/lib/okf/bundle/folder.rb +27 -1
- data/lib/okf/bundle/graph.rb +12 -3
- data/lib/okf/bundle/linter.rb +470 -47
- data/lib/okf/bundle/reader.rb +10 -4
- data/lib/okf/bundle/references.rb +111 -0
- data/lib/okf/bundle/row_filter.rb +53 -0
- data/lib/okf/bundle/search.rb +20 -2
- data/lib/okf/bundle/validator/result.rb +6 -3
- data/lib/okf/bundle/validator.rb +267 -26
- data/lib/okf/bundle/writer.rb +1 -1
- data/lib/okf/bundle.rb +105 -4
- data/lib/okf/cli/catalog.rb +1 -1
- data/lib/okf/cli/command.rb +27 -8
- data/lib/okf/cli/files.rb +1 -1
- data/lib/okf/cli/index.rb +1 -1
- data/lib/okf/cli/lint.rb +70 -12
- data/lib/okf/cli/references.rb +97 -0
- data/lib/okf/cli/search.rb +2 -1
- data/lib/okf/cli/stats.rb +3 -39
- data/lib/okf/cli/tags.rb +6 -43
- data/lib/okf/cli/types.rb +1 -1
- data/lib/okf/cli/validate.rb +3 -3
- data/lib/okf/cli.rb +4 -1
- data/lib/okf/concept.rb +362 -10
- data/lib/okf/markdown/citations.rb +41 -4
- data/lib/okf/markdown/frontmatter.rb +1 -1
- data/lib/okf/markdown/links.rb +67 -7
- data/lib/okf/render/graph/template.html.erb +173 -41
- data/lib/okf/render/graph.rb +11 -3
- data/lib/okf/server/app.rb +47 -15
- data/lib/okf/server/hub.rb +1 -1
- data/lib/okf/skill/SKILL.md +14 -12
- data/lib/okf/skill/playbooks/curate.md +8 -3
- data/lib/okf/skill/playbooks/doctor.md +3 -1
- data/lib/okf/skill/playbooks/maintain.md +7 -6
- data/lib/okf/skill/playbooks/menu.md +5 -4
- data/lib/okf/skill/playbooks/migrate.md +31 -8
- data/lib/okf/skill/playbooks/produce.md +16 -9
- data/lib/okf/skill/playbooks/search.md +2 -2
- data/lib/okf/skill/reference/SPEC.md +739 -187
- data/lib/okf/skill/reference/authoring.md +154 -35
- data/lib/okf/skill/reference/cli.md +155 -42
- data/lib/okf/skill/templates/attested-computation.md +41 -0
- data/lib/okf/skill/templates/concept.md +13 -6
- data/lib/okf/skill/templates/root-index.md +1 -1
- data/lib/okf/version.rb +1 -1
- data/lib/okf.rb +22 -2
- metadata +5 -1
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
The spec ([SPEC.md](SPEC.md)) tells you what is *legal*. This file is what is
|
|
4
4
|
*good* — the modelling judgment that turns a pile of conformant files into
|
|
5
|
-
knowledge worth consuming. Read it before `produce` or `maintain`, and keep the
|
|
6
|
-
conformance rules in mind (parseable frontmatter, a non-empty `type`, and
|
|
7
|
-
well-formed reserved files — the hard rules in [SKILL.md](../SKILL.md));
|
|
8
|
-
else is guidance a consumer must tolerate.
|
|
5
|
+
knowledge worth consuming. Read it before `produce` or `maintain`, and keep the
|
|
6
|
+
§11 conformance rules in mind (parseable frontmatter, a non-empty `type`, and
|
|
7
|
+
well-formed reserved files — the hard rules in [SKILL.md](../SKILL.md));
|
|
8
|
+
everything else is guidance a consumer must tolerate.
|
|
9
9
|
|
|
10
10
|
## What each SPEC section governs
|
|
11
11
|
|
|
@@ -15,12 +15,18 @@ Consult the right section on demand instead of re-reading all of [SPEC.md](SPEC.
|
|
|
15
15
|
|---|---------|-------------------|
|
|
16
16
|
| §3 | bundle structure, reserved filenames | laying out directories |
|
|
17
17
|
| §4 | concept documents & frontmatter | writing or validating a concept |
|
|
18
|
-
| §5
|
|
19
|
-
| §
|
|
20
|
-
| §
|
|
21
|
-
| §
|
|
22
|
-
| §
|
|
23
|
-
| §
|
|
18
|
+
| §5.1 | provenance — `sources` and its credibility signals | any external or empirical claim |
|
|
19
|
+
| §5.2 / §5.3 | trust — `generated`, `verified`, and the derived tiers | recording who wrote or confirmed content |
|
|
20
|
+
| §5.4 / §5.5 | lifecycle — `status`, `stale_after` | marking drafts, deprecations, expiries |
|
|
21
|
+
| §6 / §6.1 | cross-links; **broken links are tolerated** | linking; judging a "broken" link |
|
|
22
|
+
| §6.2 / §6.3 | path-valued fields; the `references/` convention | pointing at non-concept assets |
|
|
23
|
+
| §7 | the actor convention | filling any `by` |
|
|
24
|
+
| §8 | index files & progressive disclosure | orienting; writing or synthesizing an index |
|
|
25
|
+
| §9 | log files | recording history |
|
|
26
|
+
| §10 | attested computations | a concept that *is* a sanctioned computation |
|
|
27
|
+
| §11 | conformance — the hard gate | what `validate` may and may not reject |
|
|
28
|
+
| §12 | versioning (`okf_version`) | the root index's one allowed field |
|
|
29
|
+
| §13 | changes from v0.1 | migrating a bundle; reading an unmigrated one |
|
|
24
30
|
|
|
25
31
|
## Modelling principles
|
|
26
32
|
|
|
@@ -44,7 +50,9 @@ responsibility. Keep a **small, consistent, descriptive** vocabulary per bundle
|
|
|
44
50
|
(`Service`, `Dataset`, `Metric`, `Decision`, `Playbook`, `Runbook`, …). Reusing
|
|
45
51
|
types across files is what makes the graph legible; inventing a new type per file
|
|
46
52
|
makes `type` meaningless. Before adding a new type, check what the bundle already
|
|
47
|
-
uses.
|
|
53
|
+
uses. One type the spec *does* name: `Attested Computation` (§10) is a contract,
|
|
54
|
+
not a suggestion — use it exactly when the concept carries a sanctioned
|
|
55
|
+
computation, and use the exact spelling.
|
|
48
56
|
|
|
49
57
|
### Tags are the connective axis — curate them like a vocabulary <!-- rule:okf-tag-vocabulary -->
|
|
50
58
|
`type` says what a concept *is*; the directory says where it *lives*; `tags` are
|
|
@@ -62,15 +70,53 @@ Lay out directories by what the knowledge is *about* (`services/`, `datasets/`,
|
|
|
62
70
|
`decisions/`), not by concept type. The directory tree is itself knowledge — it
|
|
63
71
|
shows a reader how the system decomposes, and it usually mirrors the shape of the
|
|
64
72
|
codebase or the org. A `types/`-first layout scatters related concepts and buries
|
|
65
|
-
the domain.
|
|
73
|
+
the domain. Non-concept assets a concept points at — run instructions, attester
|
|
74
|
+
code, computation files — live under `references/` by convention (§6.3): a naming
|
|
75
|
+
convention, not a requirement, and the tools never require it.
|
|
76
|
+
|
|
77
|
+
### Hidden files are outside the bundle <!-- rule:okf-no-hidden-concepts -->
|
|
78
|
+
The reader excludes dot-prefixed files and every path under a dot-prefixed
|
|
79
|
+
directory — the Unix hidden-file convention, kept deliberately: a project root
|
|
80
|
+
often carries an installed skill (`.claude/`), templates (`.github/`) or other
|
|
81
|
+
markdown that is not knowledge, and reading a directory must not pull those in
|
|
82
|
+
as concepts. §3's taxonomy is read as covering the visible tree. The practical
|
|
83
|
+
rule: never author a concept under a hidden directory — no verb will ever see
|
|
84
|
+
it, and nothing will warn you.
|
|
85
|
+
|
|
86
|
+
### A path-valued field pointing at `references/` needs its leading `/` <!-- check:broken_attestation_ref -->
|
|
87
|
+
§6.2 gives `resource`, `sources[].resource`, `computation`, `executor.resource`
|
|
88
|
+
and `attester.resource` the same three forms as a link: a URL, a bundle-relative
|
|
89
|
+
path **beginning with `/`**, or a path relative to the concept. So a concept at
|
|
90
|
+
`metrics/revenue.md` writing `attester: { resource: references/attesters/rev.py }`
|
|
91
|
+
is naming `metrics/references/attesters/rev.py`, not the bundle's `references/`
|
|
92
|
+
tree — the trap being that §6.3's own example, and the SPEC's Appendix, spell
|
|
93
|
+
these paths bare from a nested concept. Write `/references/…` and the field
|
|
94
|
+
resolves from the bundle root wherever the concept sits, which is the same reason
|
|
95
|
+
[links](#links-are-untyped-on-purpose) prefer the absolute form. `lint` catches
|
|
96
|
+
the `.md` cases (`broken_attestation_ref`, `broken_source`); a `.sql` or `.py`
|
|
97
|
+
target is not a concept, so no lint check sees it — `okf references` is the
|
|
98
|
+
surface that does: it lists the `references/` tree from disk with every pointer
|
|
99
|
+
that misses, and names the leading-`/` fix when that is the miss.
|
|
100
|
+
|
|
101
|
+
### A frontmatter `id` renames the concept, not its home <!-- rule:okf-id-extension -->
|
|
102
|
+
§2 defines the Concept ID as the file's path with `.md` removed, full stop.
|
|
103
|
+
This gem additionally honors a frontmatter `id:` as an override — an okf
|
|
104
|
+
extension, not spec, so a bundle leaning on it is trading portability for the
|
|
105
|
+
alias. If you pin one, know the recorded split: links still resolve by path,
|
|
106
|
+
so edges land correctly; the identity views (catalog, hubs, search, `--dir`)
|
|
107
|
+
follow the id, because the edges do; the physical views (`index`, `dirs`,
|
|
108
|
+
stats' `by_dir`) keep the file where it lives, because an index is a physical
|
|
109
|
+
listing. A pinned id that disagrees with the path therefore makes the two
|
|
110
|
+
families answer differently about where the concept is — prefer the default,
|
|
111
|
+
and rename the file when a concept needs a new name.
|
|
66
112
|
|
|
67
113
|
### `resource` is the bridge to reality <!-- rule:okf-resource-bridge -->
|
|
68
114
|
Set `resource` (a canonical URI) **only** when a concept *is* a real, addressable
|
|
69
115
|
asset — a table (`bigquery://…`), a service repo, a dashboard, an endpoint. Its
|
|
70
116
|
presence is what lets `maintain` find every concept affected by a changed asset in
|
|
71
|
-
one `okf search <dir> <uri>` call. Abstract concepts — a decision, a principle, a
|
|
72
|
-
definition — have no resource, and **omitting it is meaningful**, not
|
|
73
|
-
not invent placeholder URIs.
|
|
117
|
+
one `okf search <dir> <uri>` call. Abstract concepts — a decision, a principle, a
|
|
118
|
+
metric definition — have no resource, and **omitting it is meaningful**, not
|
|
119
|
+
laziness. Do not invent placeholder URIs.
|
|
74
120
|
|
|
75
121
|
### Links are untyped on purpose <!-- rule:okf-untyped-links -->
|
|
76
122
|
A markdown link asserts only "these two relate." The *kind* of relationship —
|
|
@@ -78,15 +124,60 @@ depends-on, supersedes, derived-from, owns — lives in the **prose around the
|
|
|
78
124
|
link**, never in a made-up typed-edge syntax. Write the sentence that explains the
|
|
79
125
|
relationship and put the link inside it. Prefer absolute bundle-relative targets
|
|
80
126
|
(`/services/auth-api.md`) so links survive file moves. A link to a concept that
|
|
81
|
-
does not exist yet is fine — it is not-yet-written knowledge (§
|
|
82
|
-
backlog will surface it as demand.
|
|
127
|
+
does not exist yet is fine — it is not-yet-written knowledge (§6.1), and `lint`'s
|
|
128
|
+
backlog will surface it as demand. One more edge you get for free: a
|
|
129
|
+
`sources[].resource` naming another concept **is** a graph edge (§5.1's lineage),
|
|
130
|
+
so recording provenance is also linking.
|
|
83
131
|
|
|
84
|
-
### Provenance
|
|
132
|
+
### Provenance lives in `sources`, attribution in footnotes (§5.1) <!-- check:uncited_external -->
|
|
85
133
|
Any external or empirical claim — a latency number, an approval, a quota, a
|
|
86
|
-
"because X team decided Y" — should
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
"because X team decided Y" — should be traceable to a `sources:` entry. Each
|
|
135
|
+
entry carries at least a `resource` (a URL, a bundle-relative path, or a scope
|
|
136
|
+
descriptor like `all queries in project X`), and optionally an `id`, `title`,
|
|
137
|
+
and the credibility signals `author` (an actor), `usage_count` (a liveness
|
|
138
|
+
signal, never a score), and `last_modified` (`YYYY-MM-DD`). To attribute a
|
|
139
|
+
*specific claim*, give the source an `id` and cite it with a markdown footnote
|
|
140
|
+
whose label is that id: `sharded daily.[^ga4-schema]` — keyed, not positional,
|
|
141
|
+
so a reordered list cannot misattribute silently. `lint`'s provenance category
|
|
142
|
+
checks the join in both directions (`unattributed_claim`, `unused_source`).
|
|
143
|
+
Uncited claims are exactly how a bundle decays into folklore nobody trusts.
|
|
144
|
+
("Citations" is the legacy v0.1 spelling: provenance lived in a body
|
|
145
|
+
`# Citations` list, which v0.2 retires — §13.1 keeps it readable, and `lint`'s
|
|
146
|
+
Migration findings tell you what to move where.)
|
|
147
|
+
|
|
148
|
+
### Say who did it, in §7's spelling <!-- check:unprefixed_actor -->
|
|
149
|
+
Every `by` — `generated.by`, `verified[].by` — takes an actor in one of §7's
|
|
150
|
+
three forms: `<producer>/<version>` (an automated producer, e.g.
|
|
151
|
+
`reference_agent/gemini-2.5-pro`), `human:<id>`, or `process:<id>`. The one
|
|
152
|
+
producer MUST: content a person hand-authored **or confirmed** is marked
|
|
153
|
+
`human:<id>` — §5.3 derives the trust tier from exactly this prefix, so a human
|
|
154
|
+
sign-off written `by: owner` silently reads as *machine-confirmed*, the exact
|
|
155
|
+
downgrade the tier system exists to prevent (`lint`'s `unprefixed_actor` nudges
|
|
156
|
+
it). `sources[].author` is looser on purpose: §7 does not govern it, and the
|
|
157
|
+
SPEC's own examples use `team:<id>` there — any honest attribution works.
|
|
158
|
+
|
|
159
|
+
### Trust is derived, never stored (§5.2/§5.3) <!-- rule:okf-trust-derived -->
|
|
160
|
+
`generated` records how the current content was produced (`by` REQUIRED within;
|
|
161
|
+
`at` an ISO 8601 datetime); `verified` lists who confirmed it against its
|
|
162
|
+
sources — they stay separate because who *wrote* a concept need not be who
|
|
163
|
+
*checked* it. Consumers derive the tier: no `verified` ⇒ unverified; machine
|
|
164
|
+
actors only ⇒ machine-confirmed; any `human:` verifier ⇒ human-reviewed. Never
|
|
165
|
+
write a tier or a credibility score into frontmatter — it would be subjective,
|
|
166
|
+
unportable, and stale the moment the next verification lands. And never invent
|
|
167
|
+
provenance: a concept whose history you do not know is honestly unverified.
|
|
168
|
+
Prefer **block style** for these mappings (`generated:` with indented
|
|
169
|
+
`by:`/`at:`): the flow spelling (`{ by: human:x, at: …T10:00:00Z }`) is legal
|
|
170
|
+
YAML, but the colons inside its values trip older libyaml parsers, and block
|
|
171
|
+
style is the spelling every parser accepts.
|
|
172
|
+
|
|
173
|
+
### Lifecycle: `status` and `stale_after` (§5.4/§5.5) <!-- rule:okf-lifecycle -->
|
|
174
|
+
`status` is one of `draft | stable | deprecated`; absent means `stable`, so
|
|
175
|
+
declare it only when it says something. `stale_after` is an absolute
|
|
176
|
+
`YYYY-MM-DD` — the author's own "do not trust me past this date", stale **on**
|
|
177
|
+
the day itself. Use it for knowledge with a known shelf life (a quota, a
|
|
178
|
+
migration window); `lint`'s `expired` check reports the ones whose date has
|
|
179
|
+
passed. It is a declared expiry, distinct from the `--stale-after` *flag*,
|
|
180
|
+
which is a reader-supplied age cutoff — see [cli.md](cli.md).
|
|
90
181
|
|
|
91
182
|
### Capture the non-obvious — not what code already says <!-- rule:okf-non-obvious -->
|
|
92
183
|
A bundle that restates function signatures or config keys goes stale the moment
|
|
@@ -98,19 +189,46 @@ to it rather than duplicating it.
|
|
|
98
189
|
|
|
99
190
|
### Write for both readers at once <!-- rule:okf-dual-audience -->
|
|
100
191
|
Use structural markdown so an agent can extract deterministically and a human can
|
|
101
|
-
skim: headings, tables, fenced code, lists. Conventional headings a reader
|
|
102
|
-
are `# Schema` (field/column tables), `# Examples`, and
|
|
103
|
-
|
|
104
|
-
whenever it aids consumption.
|
|
192
|
+
skim: headings, tables, fenced code, lists. Conventional headings a reader
|
|
193
|
+
expects are `# Schema` (field/column tables), `# Examples`, and — on an Attested
|
|
194
|
+
Computation only — `# Computation` (§10.3). Fill recommended frontmatter —
|
|
195
|
+
`title`, `description`, `tags`, `generated` — whenever it aids consumption.
|
|
196
|
+
|
|
197
|
+
### Attested computations are contracts (§10) <!-- rule:okf-computation-untouched -->
|
|
198
|
+
A concept of type `Attested Computation` carries a sanctioned computation:
|
|
199
|
+
`runtime` is REQUIRED (it fixes what `parameters` mean), the computation is
|
|
200
|
+
provided **exactly one way** — a body `# Computation` fence *or* a
|
|
201
|
+
`computation:` path, never both — and `executor`/`attester` name how it runs
|
|
202
|
+
and how a run is checked. The one new MUST NOT in v0.2 binds the *consumer*: an
|
|
203
|
+
agent MAY bind values for the declared `parameters` and MUST NOT author or edit
|
|
204
|
+
the computation itself. A "fixed" query silently stops being the one a person
|
|
205
|
+
sanctioned; if it is wrong, tell a human. The
|
|
206
|
+
[attested-computation template](../templates/attested-computation.md) carries
|
|
207
|
+
the whole contract.
|
|
105
208
|
|
|
106
209
|
### Reserved files <!-- rule:okf-reserved-files -->
|
|
107
210
|
`index.md` is a directory listing and carries **no frontmatter** — with one
|
|
108
211
|
exception: the **bundle-root** `index.md` is the only index that may carry
|
|
109
|
-
frontmatter, and it may carry *only* `okf_version: "0.
|
|
110
|
-
flags any other key there
|
|
111
|
-
Never use these names for
|
|
112
|
-
|
|
113
|
-
|
|
212
|
+
frontmatter, and it may carry *only* `okf_version: "0.2"` (§8 states the
|
|
213
|
+
exception inline; `validate` flags any other key there under §11 condition 3).
|
|
214
|
+
`log.md` is an ISO-dated change history, newest first. Never use these names for
|
|
215
|
+
concepts. Templates: [concept](../templates/concept.md),
|
|
216
|
+
[attested computation](../templates/attested-computation.md),
|
|
217
|
+
nested [index](../templates/index.md), bundle-root
|
|
218
|
+
[root-index](../templates/root-index.md), [log](../templates/log.md).
|
|
219
|
+
|
|
220
|
+
## Migrating from v0.1 (§13)
|
|
221
|
+
|
|
222
|
+
A v0.1 bundle is consumable forever — §13.1 sanctions reading `timestamp` as
|
|
223
|
+
`generated.at` and a `# Citations` list as `sources` — so migration is
|
|
224
|
+
convenience, not rescue. Exactly two spellings retire: `timestamp` (move the
|
|
225
|
+
value under `generated: { by: <actor>, at: … }` — the actor is the one thing no
|
|
226
|
+
tool can derive; ask, or write `human:<maintainer>` when the history is human)
|
|
227
|
+
and the body `# Citations` section (lift each item into `sources`, add `id`s
|
|
228
|
+
and `[^id]` footnotes where the body cites, delete the section). `lint`'s two
|
|
229
|
+
Migration findings name the files; the [migrate playbook](../playbooks/migrate.md)
|
|
230
|
+
walks the rewrite, and `okf lint <dir> --only legacy_timestamp,legacy_citations
|
|
231
|
+
--fail-on info` is the mechanical done-check.
|
|
114
232
|
|
|
115
233
|
## Playbooks
|
|
116
234
|
|
|
@@ -130,9 +248,10 @@ grep can't:
|
|
|
130
248
|
removed is updated; re-run `okf index` and eyeball each listing against reality.
|
|
131
249
|
Easy to skip, expensive to miss — this is the check that was missing.
|
|
132
250
|
- **`log.md`** — a dated entry, newest first.
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
- **`
|
|
136
|
-
|
|
251
|
+
- **`generated.at`** bumped on the concepts you touched (and `generated.by` says
|
|
252
|
+
who touched them — you, in §7's spelling).
|
|
253
|
+
- **`validate`** — zero §11 errors.
|
|
254
|
+
- **`lint`** — cheap findings cleared; `expired` reports out of the box, and
|
|
255
|
+
`--stale-after` adds the reader-side age cutoff when you want one.
|
|
137
256
|
- **`loose` review + tag curation** — the two semantic passes (maintain steps 6–7);
|
|
138
257
|
worth a pass in `produce` too on a non-trivial bundle.
|
|
@@ -65,7 +65,7 @@ or `okf index @` work from any directory, no path recall needed.
|
|
|
65
65
|
|
|
66
66
|
**Exit codes:** `0` success · `1` non-conformant bundle (or a `lint --fail-on`
|
|
67
67
|
threshold crossed) · `2` usage error. `graph`, `server`, and `render` are best-effort
|
|
68
|
-
(§
|
|
68
|
+
(§11): a file the reader cannot use — frontmatter that will not parse, or a file it
|
|
69
69
|
cannot open at all — is skipped and noted on stderr, never fatal. The note counts;
|
|
70
70
|
`validate` names each file and why.
|
|
71
71
|
|
|
@@ -74,43 +74,95 @@ cannot open at all — is skipped and noted on stderr, never fatal. The note cou
|
|
|
74
74
|
or a mix — and it is a usage error (exit 2), never a silent answer about the first.
|
|
75
75
|
To ask the same question of several bundles, ask `search`, or ask each in turn.
|
|
76
76
|
|
|
77
|
-
## validate — the hard gate (§
|
|
77
|
+
## validate — the hard gate (§11)
|
|
78
78
|
|
|
79
|
-
Implements the spec's §
|
|
79
|
+
Implements the spec's §11 conformance definition exactly:
|
|
80
80
|
|
|
81
|
-
- **§
|
|
82
|
-
- **§
|
|
83
|
-
- **§
|
|
84
|
-
no frontmatter, a root `index.md` carries only `okf_version`,
|
|
85
|
-
headings are ISO `YYYY-MM-DD`).
|
|
81
|
+
- **§11 cond. 1** every non-reserved file has a parseable YAML frontmatter block;
|
|
82
|
+
- **§11 cond. 2** every such block has a non-empty `type`;
|
|
83
|
+
- **§11 cond. 3** any `index.md`/`log.md` present follows §8/§9 (a nested
|
|
84
|
+
`index.md` has no frontmatter, a root `index.md` carries only `okf_version`,
|
|
85
|
+
`log.md` date headings are ISO `YYYY-MM-DD`).
|
|
86
86
|
|
|
87
87
|
`ERROR`s are the three conditions above; the bundle is non-conformant until every
|
|
88
88
|
one is fixed. `warn`s are soft — missing recommended fields, non-list tags, an
|
|
89
|
-
unparseable timestamp,
|
|
90
|
-
|
|
89
|
+
unparseable timestamp, **broken cross-links, which §6.1 explicitly tolerates**,
|
|
90
|
+
the shape of every §5/§10 family (`generated` not a mapping, a non-integer
|
|
91
|
+
`usage_count`, a `stale_after` that is not `YYYY-MM-DD`, a missing `runtime` on
|
|
92
|
+
an Attested Computation, …), and an `okf_version` the gem does not know (read
|
|
93
|
+
best-effort under §12; an absent one never warns). Absence of an optional family
|
|
94
|
+
is never a fault — a pure v0.1 bundle validates with zero warnings.
|
|
95
|
+
|
|
96
|
+
In `--json`, every warning carries `check` (a stable id) and `source` — `spec`
|
|
97
|
+
when the SPEC's own words state the rule, `convention` for a shape this gem asks
|
|
98
|
+
for beyond them (`verified[].by` presence, integer `usage_count`, a per-entry
|
|
99
|
+
`usage_window` mapping, `parameters[].name`, `executor`/`attester` `resource`).
|
|
100
|
+
Gate on `source` when you want only the spec-normative set; errors keep their
|
|
101
|
+
two-key `{ path, message }` shape. Fix warnings when cheap; never block on them.
|
|
102
|
+
Use `--json` in CI.
|
|
91
103
|
|
|
92
104
|
## lint — curation quality (advisory)
|
|
93
105
|
|
|
94
106
|
Asks the complementary question to `validate`: not "is this legal OKF?" but "is
|
|
95
|
-
this well-curated, navigable, trustworthy?" — precisely over the things §
|
|
96
|
-
`validate` from rejecting. It has its own report, never emits
|
|
97
|
-
and **exits `0` even with findings** unless you pass
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
- **
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
this well-curated, navigable, trustworthy?" — precisely over the things §11
|
|
108
|
+
forbids `validate` from rejecting. It has its own report, never emits
|
|
109
|
+
conformance errors, and **exits `0` even with findings** unless you pass
|
|
110
|
+
`--fail-on warn` (exit 1 on any `warn` finding) or `--fail-on info` (exit 1 on
|
|
111
|
+
any finding at all).
|
|
112
|
+
|
|
113
|
+
**Severity is API.** Every check has a pinned level — `warn` or `info` — and
|
|
114
|
+
machine consumers gate on it, so the levels below are stable, not advisory. A
|
|
115
|
+
finding you want to gate on that is `info` gets `--fail-on info` (usually with
|
|
116
|
+
`--only`), never a hope that its severity changes.
|
|
117
|
+
|
|
118
|
+
Eight categories, each backed by individual checks (severity in brackets):
|
|
119
|
+
|
|
120
|
+
- **Reachability** — `orphan` [warn], `not_in_index` [warn],
|
|
121
|
+
`disconnected_component` [info], `unlinked` [info]
|
|
122
|
+
- **Backlog** — `missing_concept` [info], `broken_index_entry` [warn]
|
|
123
|
+
- **Completeness** — `stub` [info], `missing_title` [info],
|
|
124
|
+
`missing_description` [info], `missing_generated` [info] (quiet on either
|
|
125
|
+
spelling — a legacy `timestamp` still counts as a recorded change)
|
|
126
|
+
- **Freshness** — `expired` [info] (§5.5: past the concept's own declared
|
|
127
|
+
`stale_after`, on the day itself), `stale` [warn] (older than the
|
|
128
|
+
reader-supplied `--stale-after` cutoff, keyed on `generated_at`)
|
|
129
|
+
- **Provenance** — `uncited_external` [info] (external body links and no
|
|
130
|
+
sources, in either spelling), `broken_source` [warn] (an in-bundle `.md`
|
|
131
|
+
source target that names no concept; URLs and scope descriptors are out of
|
|
132
|
+
scope, and a non-`.md` asset is out of reach — the reader models concepts,
|
|
133
|
+
so lint never sees the file; `okf references` is the view that checks those
|
|
134
|
+
pointers), `unattributed_claim` [warn] (a footnote
|
|
135
|
+
no `sources[].id` answers — it *misattributes* a claim, which is why it
|
|
136
|
+
outranks its join-twin), `unused_source` [info] (a keyed source no footnote
|
|
137
|
+
cites — slack, not a defect), `unprefixed_actor` [info] (a `verified[].by`
|
|
138
|
+
outside §7's three forms reads as machine-confirmed; a `generated.by`
|
|
139
|
+
outside them feeds no tier but leaves a reader unable to tell a person
|
|
140
|
+
from a process; info so it informs, never blocks). A missing `generated.by` is the *validator's* warning —
|
|
141
|
+
REQUIRED-within is shape, not curation — so lint never double-reports it
|
|
142
|
+
- **Attestation** — `incomplete_computation` [warn] (an Attested Computation
|
|
143
|
+
providing its computation neither way, or both ways — §10.3 says a
|
|
144
|
+
`computation:` path is used *instead of* the body fence),
|
|
145
|
+
`broken_attestation_ref` [warn] (on an `Attested Computation`, a
|
|
146
|
+
`computation`, `executor.resource` or `attester.resource` naming an
|
|
147
|
+
in-bundle `.md` that is not there — a contract no consumer can follow; the
|
|
148
|
+
keys are read only on that type, since §4.1 lets any other concept use them
|
|
149
|
+
for its own purpose). Its reach is exactly the `.md` files: URLs are out
|
|
150
|
+
of scope, and a `.sql` or `.py` target is invisible to *every* check here,
|
|
151
|
+
because the linter reads the concept model and the model carries only
|
|
152
|
+
markdown — `okf references` is the surface that sees those files and reports
|
|
153
|
+
a pointer that misses, whatever the extension. Remember §6.2 reads a bare
|
|
154
|
+
`references/…` as relative to the concept, so from a nested concept it wants
|
|
155
|
+
the leading `/`
|
|
156
|
+
- **Migration** — `legacy_timestamp` [info], `legacy_citations` [info]: one
|
|
157
|
+
finding per bundle naming the files still in a retired v0.1 spelling, with
|
|
158
|
+
the rewrite instructions in the message. Info on purpose — §13 says a v0.1
|
|
159
|
+
bundle is consumable forever, so `--fail-on warn` must not turn red on one.
|
|
160
|
+
A migration campaign gates explicitly:
|
|
161
|
+
`okf lint <dir> --only legacy_timestamp,legacy_citations --fail-on info`,
|
|
162
|
+
exit 1 until clean.
|
|
163
|
+
- **Hygiene** — `duplicate_title` [info], `unused_reference_def` [info],
|
|
164
|
+
`undefined_reference` [warn], `self_link` [info], `log_order` [info] (§9
|
|
165
|
+
reads a log newest-first; disorder is slack, never a §11 error)
|
|
114
166
|
|
|
115
167
|
`--only` / `--except` filter by the **individual check names above**, not the
|
|
116
168
|
category labels — `okf lint <dir> --only orphan,stub` works; `--only reachability`
|
|
@@ -119,6 +171,26 @@ threshold in characters (default 50), and `--stale-after DUR` sets the `stale`
|
|
|
119
171
|
cutoff — a duration like `90d` or `12w`, or an ISO date like `2026-01-01` (a bare
|
|
120
172
|
number is rejected).
|
|
121
173
|
|
|
174
|
+
**Two different clocks, one unlucky name.** The `--stale-after` *flag* and the
|
|
175
|
+
`stale_after:` *frontmatter field* are different mechanisms that happen to share
|
|
176
|
+
a spelling. The flag is the **reader's** age cutoff: "flag anything not touched
|
|
177
|
+
since DUR", keyed on `generated_at`, feeding the `stale` check. The field is the
|
|
178
|
+
**author's** declared expiry: "do not trust this past DATE", feeding the
|
|
179
|
+
`expired` check. Never read one as the other, and never show them adjacent
|
|
180
|
+
without the distinction.
|
|
181
|
+
|
|
182
|
+
**The clock is explicit.** `expired` compares against a day the CLI supplies —
|
|
183
|
+
today by default, or `--today YYYY-MM-DD` for a reproducible report (CI wants
|
|
184
|
+
this). The pure library runs no clock check unless handed `today:`, and every
|
|
185
|
+
clock-gated check that was selected but could not run is *named* in
|
|
186
|
+
`stats.skipped_checks` (the human report prints one `skipped:` line) — a gate
|
|
187
|
+
that is sometimes absent and does not confess converts "unchecked" into
|
|
188
|
+
"checked and fine".
|
|
189
|
+
|
|
190
|
+
The report's stats carry the bundle's posture too: `trust` (the §5.3 tier
|
|
191
|
+
distribution, in the hyphenated wire spelling) and `status` (effective-status
|
|
192
|
+
frequency).
|
|
193
|
+
|
|
122
194
|
`lint --json` is the structured substrate you consume to reason about the two
|
|
123
195
|
things lint deliberately does **not** compute — contradictions and *semantic*
|
|
124
196
|
staleness — which need understanding of meaning.
|
|
@@ -150,8 +222,10 @@ as Ruby regular expressions with `--regexp`/`-e` (an invalid pattern is a usage
|
|
|
150
222
|
error, exit 2). `--fuzzy` forgives typos; pairing it with `-e` is a usage error,
|
|
151
223
|
since a pattern is matched literally rather than by edit distance.
|
|
152
224
|
`--in a,b` restricts the searched fields (title, id, tags, type, description,
|
|
153
|
-
body
|
|
154
|
-
|
|
225
|
+
sources, body — `sources` is each entry's title and resource joined, so a
|
|
226
|
+
migrated bundle keeps the recall its `# Citations` body text used to give it);
|
|
227
|
+
the shared `--type/--dir/--tag/--status/--trust` filters narrow the candidates
|
|
228
|
+
*first*, so a search scoped by what `index` taught you stays surgical.
|
|
155
229
|
|
|
156
230
|
**The default is exact, so an exact query means what it looks like.** A phrase in
|
|
157
231
|
one argument (`"dedup key"`), a dotted version (`7.2.0`), an underscored
|
|
@@ -261,7 +335,7 @@ same-id concepts from different bundles become indistinguishable. The retrieval
|
|
|
261
335
|
map first, finder second, bodies last — is the
|
|
262
336
|
[search playbook](../playbooks/search.md).
|
|
263
337
|
|
|
264
|
-
## index — the progressive-disclosure map (§
|
|
338
|
+
## index — the progressive-disclosure map (§8)
|
|
265
339
|
|
|
266
340
|
The "orient before you read" view, and the read verb that sees the layer the
|
|
267
341
|
concept views can't: `index.md` files are reserved/structural, so
|
|
@@ -300,7 +374,7 @@ and `--except body,listing` on top of either is the lean JSON skeleton. Full
|
|
|
300
374
|
`--no-body` drops the prose to a
|
|
301
375
|
skeleton (headers, rollups, child pointers). For a directory that has concepts but
|
|
302
376
|
**no `index.md`**, the listing is **synthesized** from the concepts' descriptions
|
|
303
|
-
and tagged `(no index.md)` — §
|
|
377
|
+
and tagged `(no index.md)` — §8 explicitly permits synthesizing a map on the fly.
|
|
304
378
|
|
|
305
379
|
It is a **read view**: advisory, always exit 0. A synthesized directory is a
|
|
306
380
|
*signal* (a map worth writing), never a defect — `index` emits no lint findings and
|
|
@@ -357,11 +431,22 @@ verbs reproduce them on the CLI so an agent can read a bundle without a browser.
|
|
|
357
431
|
All are advisory reads (exit 0) sharing one data source (per-concept metadata plus
|
|
358
432
|
in/out link degree). Add `--json` to any for a machine substrate.
|
|
359
433
|
|
|
360
|
-
- **`catalog`** — every concept with its metadata (type, status,
|
|
361
|
-
in/out link degree, description), grouped by top-level dir (`dir`
|
|
362
|
-
carries the full path, `top_dir` the first segment). The "what's
|
|
363
|
-
detail" view. JSON: `{ bundle, count, concepts: [{ id, title, type,
|
|
364
|
-
|
|
434
|
+
- **`catalog`** — every concept with its metadata (type, status, trust, tags,
|
|
435
|
+
provenance, in/out link degree, description), grouped by top-level dir (`dir`
|
|
436
|
+
on every row carries the full path, `top_dir` the first segment). The "what's
|
|
437
|
+
here, in detail" view. JSON: `{ bundle, count, concepts: [{ id, title, type,
|
|
438
|
+
description, tags, generated_at, generated_by, generated, trust, status,
|
|
439
|
+
stale_after, sources, backlog_ref, dir, top_dir, links_out, links_in }] }`.
|
|
440
|
+
Four of those deserve a sentence: `generated` is the raw boolean ("does the
|
|
441
|
+
document *declare* a generated mapping"), which is what tells hand-written
|
|
442
|
+
apart from v0.1-with-timestamp — `generated_at` alone conflates them, because
|
|
443
|
+
§13.1 lifts a legacy `timestamp` into it. `trust` is the derived §5.3 tier as
|
|
444
|
+
a hyphenated literal (`unverified` | `machine-confirmed` | `human-reviewed`) —
|
|
445
|
+
compare against exactly those. `status` is the *declared* value, `null` when
|
|
446
|
+
absent (the row never fabricates frontmatter; the `--status` filter is what
|
|
447
|
+
applies the §5.4 default). `sources` is a count. Temporal fields render
|
|
448
|
+
ISO 8601 (`stale_after` as `YYYY-MM-DD`). The `timestamp` column is retired —
|
|
449
|
+
`--fields timestamp` is a usage error naming the valid fields.
|
|
365
450
|
- **`files`** — the folder tree: each concept's filename + title, grouped by
|
|
366
451
|
directory. The "how it's organised" view. JSON: `{ bundle, count, files: [{ path,
|
|
367
452
|
id, dir, type, title, description }] }`.
|
|
@@ -389,8 +474,14 @@ in/out link degree). Add `--json` to any for a machine substrate.
|
|
|
389
474
|
can address.
|
|
390
475
|
|
|
391
476
|
The four list views narrow with the same filters the browser panels offer —
|
|
392
|
-
`--type TYPE`, `--dir PATH`, `--tag TAG
|
|
393
|
-
|
|
477
|
+
`--type TYPE`, `--dir PATH`, `--tag TAG`, `--status STATUS`, `--trust TIER`
|
|
478
|
+
(`search` takes them too); each takes the ones orthogonal to itself (`tags`
|
|
479
|
+
can't filter by tag). `--status` matches the *effective* status (absent reads
|
|
480
|
+
`stable`, §5.4) and `--trust` the derived tier, either spelling
|
|
481
|
+
(`machine-confirmed` or `machine_confirmed`) — on a v0.1 bundle
|
|
482
|
+
`--status stable` and `--trust unverified` match everything, which is §13.1
|
|
483
|
+
reading, not an error, and an unknown value matches nothing at exit 0.
|
|
484
|
+
Matching is case-insensitive; `--type` and
|
|
394
485
|
`--tag` are exact, `--dir` takes the named directory **and everything below it**
|
|
395
486
|
(`--dir platform` reaches `platform/services/api`). A concept at the bundle root
|
|
396
487
|
lives in `.`, which `--dir` also accepts as plain `root` (no shell quoting) —
|
|
@@ -411,6 +502,28 @@ plus whatever the other flag selected: an answer to neither question.
|
|
|
411
502
|
Reach for `stats` first to size a bundle, `catalog`/`files` to enumerate it, `tags`
|
|
412
503
|
to find thematic clusters — all without standing up the server.
|
|
413
504
|
|
|
505
|
+
## references — the `references/` inventory (§6.3)
|
|
506
|
+
|
|
507
|
+
Lists every file under `references/` — including the non-markdown ones no other
|
|
508
|
+
verb can see, since the concept model carries only markdown — with which
|
|
509
|
+
concepts cite each file through the §6.2 path-valued fields (`resource`,
|
|
510
|
+
`sources[].resource`, `computation`, `executor.resource`, `attester.resource`),
|
|
511
|
+
plus every pointer into `references/` that resolves to nothing. Advisory:
|
|
512
|
+
**exits `0`** even with dangling pointers — the findings are the output. JSON:
|
|
513
|
+
`{ bundle, dangling, count, references: [{ path, dir, kind, referenced_by }] }`,
|
|
514
|
+
with `--fields`/`--except` projecting the rows. A file that is itself a concept
|
|
515
|
+
(§6.3 allows both) is marked `kind: "concept"`; body links are the graph's
|
|
516
|
+
business and are not counted here.
|
|
517
|
+
|
|
518
|
+
**The dangling list is where §6.2's bare-path trap surfaces.** A bare
|
|
519
|
+
`references/attesters/rev.py` written from `metrics/` resolves relative to the
|
|
520
|
+
concept — `metrics/references/attesters/rev.py`, nothing — and when the
|
|
521
|
+
leading-slash spelling would have hit, the entry says so:
|
|
522
|
+
`/references/attesters/rev.py exists — missing leading slash?`. Reach is any
|
|
523
|
+
extension, which is exactly what `broken_source` and `broken_attestation_ref`
|
|
524
|
+
cannot offer (their exemptions above), so run it wherever a bundle carries
|
|
525
|
+
attester code or computation files.
|
|
526
|
+
|
|
414
527
|
## server — interactive graph server
|
|
415
528
|
|
|
416
529
|
Starts a local HTTP server (`okf server <dir>`; `-p`/`--port`, default 8808, and
|
|
@@ -429,7 +542,7 @@ select — depth 1 is the flat view, and a flat bundle is offered no control. Th
|
|
|
429
542
|
UI too: the Files view carries **Files | Indexes** tabs — the Indexes tab
|
|
430
543
|
lists the log first (the chronological index), then every `index.md` — and
|
|
431
544
|
folder nodes in file-tree mode and directory boxes in cluster mode open a
|
|
432
|
-
directory's §
|
|
545
|
+
directory's §8 map in the inspector (authored, or synthesized when none
|
|
433
546
|
exists). Links to an `index.md`, `log.md`, or bare directory navigate instead
|
|
434
547
|
of dead-ending, and the log is fetched fresh on every read, so a
|
|
435
548
|
just-appended entry shows without a restart. `?view=index` jumps straight to
|
|
@@ -552,7 +665,7 @@ Prints the node/edge graph. `--json` emits a machine-readable dump — the
|
|
|
552
665
|
`id`/`type`/`title`/`description`/`tags` **and, by default, every `body`** — the
|
|
553
666
|
part that dominates the bytes on a real bundle) plus `edges` — you can pipe into
|
|
554
667
|
other analysis. A concept with a missing *or blank* `type` indexes under
|
|
555
|
-
`Untyped`: §
|
|
668
|
+
`Untyped`: §11 condition 2 rejects both identically, so both land in one bucket. To *plan* a traversal, structure is all you need: `--no-body`
|
|
556
669
|
drops each node's body, and `--minimal` ships only `id`/`title` plus the type/tag
|
|
557
670
|
indexes — the lean shape the `server` page boots from. Reach for the full dump
|
|
558
671
|
only when the task truly consumes every body; for one question, the
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Attested Computation
|
|
3
|
+
title: <What the computation answers, e.g. Revenue for fiscal year>
|
|
4
|
+
description: <Single sentence: what this computes and per whose definition.>
|
|
5
|
+
tags: [<tag>]
|
|
6
|
+
runtime: <REQUIRED — what runs it and what parameters mean: bigquery, dbt, python, …>
|
|
7
|
+
parameters:
|
|
8
|
+
- { name: <name>, type: <type>, required: true }
|
|
9
|
+
executor:
|
|
10
|
+
resource: <path to run instructions, e.g. /references/skills/run-on-bq.md>
|
|
11
|
+
receipt: [<field>, <field>]
|
|
12
|
+
attester:
|
|
13
|
+
resource: <path to the deterministic check, e.g. /references/attesters/check.py>
|
|
14
|
+
generated:
|
|
15
|
+
by: <actor per §7>
|
|
16
|
+
at: <ISO 8601>
|
|
17
|
+
sources:
|
|
18
|
+
- id: <stable-key>
|
|
19
|
+
title: <the definition this computation implements>
|
|
20
|
+
resource: <url or path>
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Computation
|
|
24
|
+
|
|
25
|
+
<The sanctioned computation, in one fenced block — OR set `computation: <path>`
|
|
26
|
+
in the frontmatter instead and omit this section entirely (§10.3: a path is
|
|
27
|
+
used *instead of* the fence; providing both leaves a consumer two candidate
|
|
28
|
+
computations and no rule for which was sanctioned).>
|
|
29
|
+
|
|
30
|
+
```<language>
|
|
31
|
+
<the computation>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
<Prose around it may cite sources per claim.[^<stable-key>]>
|
|
35
|
+
|
|
36
|
+
<!-- rule:okf-computation-untouched -->
|
|
37
|
+
<!-- §10.3, v0.2's only new MUST NOT: an agent MAY bind values for the declared
|
|
38
|
+
`parameters` and MUST NOT author or edit the computation itself. The whole
|
|
39
|
+
point of the type is that the computation is sanctioned — reviewed by a
|
|
40
|
+
person — and an improvised or "fixed" query silently loses that status.
|
|
41
|
+
If the computation is wrong, say so to a human; do not repair it. -->
|
|
@@ -4,12 +4,23 @@ title: <Human-readable display name>
|
|
|
4
4
|
description: <Single sentence summarizing the concept.>
|
|
5
5
|
resource: <Canonical URI of the underlying asset — omit for abstract concepts>
|
|
6
6
|
tags: [<tag>, <tag>]
|
|
7
|
-
|
|
7
|
+
generated:
|
|
8
|
+
by: <actor per §7 — human:<id>, process:<id>, or <producer>/<version>>
|
|
9
|
+
at: <ISO 8601, e.g. 2026-06-14T10:00:00Z>
|
|
10
|
+
sources:
|
|
11
|
+
- id: <stable-key-the-body-cites>
|
|
12
|
+
title: <source title>
|
|
13
|
+
resource: <url, bundle-relative path, or scope descriptor>
|
|
14
|
+
# Optional §5 families, when they apply:
|
|
15
|
+
# verified: [{ by: <actor>, at: <ISO 8601> }] — who confirmed the content (a bare mapping reads as a one-element list)
|
|
16
|
+
# status: draft | stable | deprecated — absent means stable
|
|
17
|
+
# stale_after: YYYY-MM-DD — stale on the day itself
|
|
8
18
|
---
|
|
9
19
|
|
|
10
20
|
# Overview
|
|
11
21
|
|
|
12
|
-
<What this concept is and why it matters
|
|
22
|
+
<What this concept is and why it matters. Attribute each empirical claim to a
|
|
23
|
+
source by its id: "The events table is sharded daily.[^<stable-key-the-body-cites>]">
|
|
13
24
|
|
|
14
25
|
# Schema
|
|
15
26
|
|
|
@@ -18,7 +29,3 @@ timestamp: <ISO 8601, e.g. 2026-06-14T10:00:00Z>
|
|
|
18
29
|
| Field | Type | Description |
|
|
19
30
|
|-------|------|-------------|
|
|
20
31
|
| | | |
|
|
21
|
-
|
|
22
|
-
# Citations
|
|
23
|
-
|
|
24
|
-
[1] [<source title>](<url>)
|
data/lib/okf/version.rb
CHANGED