okf 1.13.0 → 2.1.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +222 -0
  3. data/README.md +34 -11
  4. data/lib/okf/bundle/folder.rb +27 -1
  5. data/lib/okf/bundle/graph.rb +12 -3
  6. data/lib/okf/bundle/linter.rb +470 -47
  7. data/lib/okf/bundle/reader.rb +10 -4
  8. data/lib/okf/bundle/references.rb +111 -0
  9. data/lib/okf/bundle/row_filter.rb +53 -0
  10. data/lib/okf/bundle/search.rb +20 -2
  11. data/lib/okf/bundle/validator/result.rb +6 -3
  12. data/lib/okf/bundle/validator.rb +267 -26
  13. data/lib/okf/bundle/writer.rb +1 -1
  14. data/lib/okf/bundle.rb +105 -4
  15. data/lib/okf/cli/catalog.rb +1 -1
  16. data/lib/okf/cli/command.rb +27 -8
  17. data/lib/okf/cli/files.rb +1 -1
  18. data/lib/okf/cli/index.rb +1 -1
  19. data/lib/okf/cli/lint.rb +70 -12
  20. data/lib/okf/cli/references.rb +97 -0
  21. data/lib/okf/cli/search.rb +2 -1
  22. data/lib/okf/cli/stats.rb +3 -39
  23. data/lib/okf/cli/tags.rb +6 -43
  24. data/lib/okf/cli/types.rb +1 -1
  25. data/lib/okf/cli/validate.rb +3 -3
  26. data/lib/okf/cli.rb +24 -3
  27. data/lib/okf/concept.rb +362 -10
  28. data/lib/okf/markdown/citations.rb +41 -4
  29. data/lib/okf/markdown/frontmatter.rb +1 -1
  30. data/lib/okf/markdown/links.rb +67 -7
  31. data/lib/okf/render/graph/template.html.erb +173 -41
  32. data/lib/okf/render/graph.rb +11 -3
  33. data/lib/okf/server/app.rb +47 -15
  34. data/lib/okf/server/hub.rb +1 -1
  35. data/lib/okf/skill/SKILL.md +28 -18
  36. data/lib/okf/skill/playbooks/curate.md +8 -3
  37. data/lib/okf/skill/playbooks/doctor.md +3 -1
  38. data/lib/okf/skill/playbooks/maintain.md +10 -7
  39. data/lib/okf/skill/playbooks/menu.md +5 -4
  40. data/lib/okf/skill/playbooks/migrate.md +31 -8
  41. data/lib/okf/skill/playbooks/produce.md +20 -11
  42. data/lib/okf/skill/playbooks/search.md +2 -2
  43. data/lib/okf/skill/reference/SPEC.md +739 -187
  44. data/lib/okf/skill/reference/authoring.md +164 -48
  45. data/lib/okf/skill/reference/cli/checks.md +171 -0
  46. data/lib/okf/skill/reference/cli/graph.md +49 -0
  47. data/lib/okf/skill/reference/cli/map.md +98 -0
  48. data/lib/okf/skill/reference/cli/registry.md +70 -0
  49. data/lib/okf/skill/reference/cli/search.md +130 -0
  50. data/lib/okf/skill/reference/cli/serve.md +83 -0
  51. data/lib/okf/skill/reference/cli/views.md +59 -0
  52. data/lib/okf/skill/reference/cli.md +44 -501
  53. data/lib/okf/skill/reference/spec-map.md +32 -0
  54. data/lib/okf/skill/templates/attested-computation.md +41 -0
  55. data/lib/okf/skill/templates/concept.md +13 -6
  56. data/lib/okf/skill/templates/root-index.md +1 -1
  57. data/lib/okf/version.rb +1 -1
  58. data/lib/okf.rb +22 -2
  59. metadata +13 -1
@@ -18,16 +18,23 @@ module OKF
18
18
  #
19
19
  # GET / the interactive graph page (text/html)
20
20
  # GET /node?id=… the concept's raw markdown body (text/markdown)
21
- # GET /node/meta?id=… its description, as an escaped HTML fragment
21
+ # GET /node/meta?id=… its description and null-stripped §5 trust fields —
22
+ # { description, trust: { tier, generated_by,
23
+ # generated_at, status, stale_after } } (JSON). The
24
+ # page composes the trust line client-side, once, for
25
+ # served and baked pages alike; expiry is the client's
26
+ # to compute, against the viewer's own today.
22
27
  # GET /catalog rich per-concept metadata for the catalog/files/stats
23
28
  # views: { concepts: [ {id, title, type, description,
24
- # tags, timestamp, status, top_dir, dir, links_*} ] } (JSON)
29
+ # tags, generated_at, generated_by, generated, trust,
30
+ # status, stale_after, sources, top_dir, dir,
31
+ # links_*} ] } (JSON)
25
32
  # GET /tags the tag index { tag => [id, …] } (JSON)
26
33
  # GET /types the type index { type => [id, …] } (JSON)
27
- # GET /index the §6 progressive-disclosure map for the Index panel:
34
+ # GET /index the §8 progressive-disclosure map for the Index panel:
28
35
  # { directories: [ …okf-index rows… ] } (JSON, from the
29
36
  # boot snapshot — authored maps are structure)
30
- # GET /log the §7 history for the Log panel: { logs: [ {path,
37
+ # GET /log the §9 history for the Log panel: { logs: [ {path,
31
38
  # dir, content} ] } (JSON; content read live from disk,
32
39
  # like a body — the log is the file that changes most)
33
40
  # GET /search?q=… ranked concepts in this bundle, for the ⌘K palette:
@@ -143,14 +150,14 @@ module OKF
143
150
  { concepts: @folder.catalog }
144
151
  end
145
152
 
146
- # The §6 map the Index panel renders — the same rows `okf index` prints,
153
+ # The §8 map the Index panel renders — the same rows `okf index` prints,
147
154
  # built by the pure OKF::Bundle#directory_index over the boot snapshot
148
155
  # (authored index bodies are structure, read at load like the graph).
149
156
  def directory_index
150
157
  { directories: @folder.directory_index }
151
158
  end
152
159
 
153
- # The §7 history the Log panel renders: every log.md with its content, root
160
+ # The §9 history the Log panel renders: every log.md with its content, root
154
161
  # scope first, read live from disk. Built by OKF::Bundle::Folder#log_entries,
155
162
  # shared with `okf render`'s bake so the served and baked logs cannot drift.
156
163
  def logs
@@ -181,11 +188,19 @@ module OKF
181
188
  respond("text/markdown; charset=utf-8", concept.body)
182
189
  end
183
190
 
191
+ # JSON, not a fragment: a server-composed HTML line would be a second
192
+ # render path reaching innerHTML outside DOMPurify — the exact hole the
193
+ # self-contained-page rule names — and would need a byte-identical JS twin
194
+ # for the baked page anyway. The client owns the one composition, and
195
+ # every string here lands via textContent there.
184
196
  def node_meta(id)
185
197
  concept = concept_for(id)
186
198
  return not_found if concept.nil?
187
199
 
188
- respond("text/html; charset=utf-8", description_fragment(concept))
200
+ payload = { "description" => concept.description.to_s }
201
+ trust = trust_fields(concept)
202
+ payload["trust"] = trust unless trust.empty?
203
+ respond_json(payload)
189
204
  end
190
205
 
191
206
  # Resolve an id to its concept, read live from disk. The id is only ever a key
@@ -200,11 +215,32 @@ module OKF
200
215
  nil
201
216
  end
202
217
 
203
- def description_fragment(concept)
204
- description = concept.description.to_s
205
- return %(<span class="empty">no description</span>) if description.strip.empty?
218
+ # Null-stripped, and absent as a whole when a concept says nothing about
219
+ # its own provenance — a v0.1 concept that adopted no §5 family gets the
220
+ # panel it always had. The tier is skipped for unverified-and-undeclared
221
+ # through Concept#shows_trust?, the one predicate the card chip and the
222
+ # page's facets read too: an untouched v0.1 bundle must not read
223
+ # "unverified" on every panel. No expiry verdict is baked (the client
224
+ # compares dates with the viewer's today), and no actor is invented for a
225
+ # lifted timestamp.
226
+ def trust_fields(concept)
227
+ fields = {}
228
+ fields["tier"] = concept.trust if concept.shows_trust?
229
+ fields["generated_by"] = concept.generated_by
230
+ fields["generated_at"] = iso(concept.generated_at)
231
+ # The row's serialization, not the raw Psych value: `status: no` reads
232
+ # as false, blank?(false) is true, and stripping it here while the
233
+ # baked page reads the row's "false" split the served and baked
234
+ # inspectors over one concept.
235
+ fields["status"] = concept.declared_status&.to_s
236
+ fields["stale_after"] = iso(concept.stale_after)
237
+ fields.reject { |_, value| OKF.blank?(value) }
238
+ end
206
239
 
207
- html_escape(description)
240
+ # The one temporal-serialization rule the catalog row keeps — shared, not
241
+ # mirrored, so /node/meta and the row cannot drift (see OKF.iso8601).
242
+ def iso(value)
243
+ OKF.iso8601(value)
208
244
  end
209
245
 
210
246
  def respond(content_type, body)
@@ -225,10 +261,6 @@ module OKF
225
261
  def not_found
226
262
  self.class.not_found
227
263
  end
228
-
229
- def html_escape(str)
230
- Rack::Utils.escape_html(str.to_s)
231
- end
232
264
  end
233
265
  end
234
266
  end
@@ -599,7 +599,7 @@ module OKF
599
599
  end
600
600
 
601
601
  # ok / warn / error, with the word that carries the same message for a
602
- # reader who cannot see the colour. validate and lint stay separate (§9):
602
+ # reader who cannot see the colour. validate and lint stay separate (§11):
603
603
  # a curation finding is a warning and never a conformance error, so a thin
604
604
  # bundle keeps its link and only a non-conformant one reads as broken.
605
605
  # Memoised like #counts — every stray 404 renders a bundle list too, and
@@ -35,13 +35,13 @@ Two ideas govern everything:
35
35
  declare a graph; it arises from how you link concepts. Good linking *is* good
36
36
  knowledge modelling.
37
37
 
38
- ## The hard rules (§9 conformance)
38
+ ## The hard rules (§11 conformance)
39
39
 
40
40
  Three conditions, all hard — `validate` fails a bundle on any of them:
41
41
 
42
- 1. **§9.1** every non-reserved `.md` file has a parseable YAML frontmatter block;
43
- 2. **§9.2** every such block has a **non-empty `type`**;
44
- 3. **§9.3** every reserved file present is well-formed — a nested `index.md` has
42
+ 1. **§11 cond. 1** every non-reserved `.md` file has a parseable YAML frontmatter block;
43
+ 2. **§11 cond. 2** every such block has a **non-empty `type`**;
44
+ 3. **§11 cond. 3** every reserved file present is well-formed — a nested `index.md` has
45
45
  no frontmatter, the bundle-root `index.md` carries *only* `okf_version`, and
46
46
  `log.md` date headings are ISO `YYYY-MM-DD`.
47
47
 
@@ -55,11 +55,11 @@ most common mistake:
55
55
 
56
56
  | Lens | Question | Tool | Nature |
57
57
  |-----------|-----------------------------------|-------------------------|---------------------------|
58
- | **Legal** | Is it conformant OKF? (§9) | `validate` | Binary, tolerant |
58
+ | **Legal** | Is it conformant OKF? (§11) | `validate` | Binary, tolerant |
59
59
  | **Good** | Is it navigable, complete, fresh? | `lint` | Advisory, structural |
60
60
  | **True** | Is it consistent and *current*? | *you*, over `lint --json` | Semantic — needs meaning |
61
61
 
62
- `validate` is *forbidden* by §9 from failing a bundle for broken links or missing
62
+ `validate` is *forbidden* by §11 from failing a bundle for broken links or missing
63
63
  optional fields — that is `lint`'s job. And neither tool can judge contradictions
64
64
  or *semantic* staleness (a concept that parses fine but no longer matches
65
65
  reality); only an agent reasoning over meaning can. That last lens is where you
@@ -81,7 +81,7 @@ read and act on, never a missing toolchain to send to doctor.
81
81
  Don't memorize the surface — `okf --help` maps every verb, `okf <verb> --help` its
82
82
  flags. The division of labour is the whole game:
83
83
 
84
- - **Shell out — never eyeball —** anything a verb computes: conformance (§9), what
84
+ - **Shell out — never eyeball —** anything a verb computes: conformance (§11), what
85
85
  exists, what links where, where a term lives, what's stale, the map. Every read
86
86
  verb takes `--json` and the list views filter by type/dir/tag, so ask the narrow
87
87
  question instead of paging the bundle.
@@ -92,21 +92,30 @@ flags. The division of labour is the whole game:
92
92
  (parses fine, no longer true), whether a loose file is terminal-by-design, whether
93
93
  a singleton tag is a deliberate marker. Tool output is evidence, never a verdict.
94
94
 
95
- The one trap worth carrying in your head: **freshness is off by default** — a plain
96
- `okf lint` never reports stale concepts; pass `--stale-after <90d|12w|ISO-date>`
97
- when the bundle carries timestamps. <!-- check:stale -->
98
-
99
- Read [cli.md](reference/cli.md) before *interpreting* a verb's output in depth:
100
- what `validate` may and may not reject, lint's categories and check ids, the JSON
101
- shapes, the tag-curation views, the server's trust boundary.
95
+ The one trap worth carrying in your head: **the age cutoff is off by default** —
96
+ a plain `okf lint` reports concepts past their own declared `stale_after` (the
97
+ `expired` check reads the clock the CLI supplies), but never judges *age*; pass
98
+ `--stale-after <90d|12w|ISO-date>` when you want anything not touched since then
99
+ flagged too the two mechanisms share a spelling and nothing else
100
+ ([cli/checks.md](reference/cli/checks.md), rule `okf-two-clocks`).
101
+ <!-- check:stale -->
102
+
103
+ Read [cli.md](reference/cli.md) before *interpreting* a verb's output in depth.
104
+ It is the index and the shared contract — refs, exit codes, `--json`, the
105
+ filters — and its table routes to the one file the verb lives in:
106
+ [checks](reference/cli/checks.md) (what `validate` may and may not reject, lint's
107
+ categories and check ids), [search](reference/cli/search.md),
108
+ [map](reference/cli/map.md) (`index`/`dirs`), [views](reference/cli/views.md)
109
+ (the tag-curation views), [serve](reference/cli/serve.md) (the trust boundary),
110
+ [registry](reference/cli/registry.md), [graph](reference/cli/graph.md).
102
111
 
103
112
  ## Orient before you touch anything
104
113
 
105
114
  Picking up a bundle you don't already know — to consume or maintain — start with
106
115
  `okf dirs <dir|@slug>`: one row per *directory*, so it stays small on a bundle of
107
116
  any size and it names the branches every other view narrows to. Then open the one
108
- you want with `okf index <dir|@slug> --dir <branch>` (the §6 map: that directory's
109
- index body, rollups, and listing), and read `log.md` (the §7 baseline of what
117
+ you want with `okf index <dir|@slug> --dir <branch>` (the §8 map: that directory's
118
+ index body, rollups, and listing), and read `log.md` (the §9 baseline of what
110
119
  changed last) — all of it **before** greping or opening leaves. Reach for `index`
111
120
  rather than grep for the one reason that outranks convenience: **grep cannot find
112
121
  an index entry that is missing**, so enumeration drift is invisible to it — you
@@ -121,8 +130,9 @@ playbooks (the Commands table below; no `okf` installed? read the root
121
130
  `consume` (use it as context) carry the judgment the executable can't — this is
122
131
  where the skill earns its keep. Each has a playbook (the Commands table below);
123
132
  read the modelling craft in [authoring.md](reference/authoring.md) before
124
- producing or maintaining, and the verbatim spec [SPEC.md](reference/SPEC.md)
125
- when you need chapter and verse.
133
+ producing or maintaining. When you need chapter and verse, go through
134
+ [spec-map.md](reference/spec-map.md) which § settles which question — into the
135
+ verbatim [SPEC.md](reference/SPEC.md).
126
136
 
127
137
  **No subcommand?** Infer intent: "document this / capture X" → `produce`;
128
138
  "convert / migrate / OKFy these existing docs into a bundle" → `migrate`; "the
@@ -20,11 +20,16 @@ mis-homed hubs, an uncurated tag layer — that is [refine](refine.md).
20
20
  `okf loose <root> --json`.
21
21
  3. Interpret through the three lenses the okf skill teaches, and keep them
22
22
  separate:
23
- - conformance errors (§9) are the only hard failures; fix them first,
23
+ - conformance errors (§11) are the only hard failures; fix them first,
24
24
  always;
25
25
  - lint findings are curation debt across reachability, backlog,
26
- completeness, freshness, provenance, and hygiene. They are advisory;
27
- rank them by how much each hurts a reader navigating the graph;
26
+ completeness, freshness, provenance, attestation, migration, and hygiene.
27
+ They are advisory; rank them by how much each hurts a reader navigating
28
+ the graph. Read the stats' `trust` and `status` distributions for the
29
+ bundle's posture (how much is unverified, what is draft or deprecated),
30
+ and treat the Migration findings as an offer, not debt — §13 keeps a v0.1
31
+ bundle consumable forever, and the [migrate playbook](migrate.md) carries
32
+ the rewrite when the user wants it;
28
33
  - loose files can be legitimate terminal leaves, so judge each one before
29
34
  linking it anywhere.
30
35
  4. Propose, then apply: list the fixes worth making (must-fix errors first,
@@ -39,7 +39,9 @@ Verify with `okf --version` before moving on.
39
39
  directory, or a root `index.md` whose frontmatter carries `okf_version`.
40
40
  2. Found one? Run `okf validate <root>` and `okf lint <root>`, then summarize
41
41
  in a few lines: conformant or not (and the errors if not), the warning
42
- count, and the top curation findings by category.
42
+ count, the top curation findings by category, the trust/status posture the
43
+ lint stats report, and — when the Migration category fires — that the bundle
44
+ still writes v0.1 spellings and the skill's migrate playbook can move them.
43
45
  3. No bundle? Offer to bootstrap one. The okf skill knows how (its "produce"
44
46
  workflow); do not scaffold anything without the user's yes.
45
47
 
@@ -6,8 +6,8 @@ up. Restructuring the bundle itself — moving concepts, adding areas — is
6
6
  lives in [authoring.md](../reference/authoring.md).
7
7
 
8
8
  1. **Orient before hunting.** Run `okf dirs <dir>` (the shape), then `okf index
9
- <dir> --dir <branch>` on the branch the change touches (the §6 map: its index
10
- body, rollups and listing), read `log.md` (the §7 baseline: what changed
9
+ <dir> --dir <branch>` on the branch the change touches (the §8 map: its index
10
+ body, rollups and listing), read `log.md` (the §9 baseline: what changed
11
11
  last), and `okf stats <dir>` (size and shape) *before* you grep. It is the
12
12
  cheapest context and it primes the hunt — and it is the only reliable way to
13
13
  catch enumeration drift, because **grep cannot find an index entry that is
@@ -19,18 +19,21 @@ lives in [authoring.md](../reference/authoring.md).
19
19
  cannot express — and `okf graph <dir> --json --minimal` to pull the edges (the
20
20
  concepts that link *to* the ones you're touching) without paying for every body.
21
21
  Let search and the graph find them so nothing drifts silently.
22
- 3. Update bodies and `timestamp`; fix or add cross-links; create new concepts for
22
+ 3. Update bodies and `generated.at` (and `generated.by` who is making this
23
+ change, in §7's spelling); fix or add cross-links; create new concepts for
23
24
  new assets; mark retired assets with a `**Deprecation**` note rather than
24
25
  silently deleting the context that explains them.
25
26
  4. **Update every enumeration that names what you changed — including `index.md`
26
27
  bodies**, not just the concept files: a new, renamed, or removed concept changes
27
- its directory's index listing too. Append a dated `log.md` entry. Step 1's map
28
+ its directory's index listing too. Append a dated `log.md` entry what changed and why it
29
+ matters, never the rounds it took (rule `okf-log-durable-only` in
30
+ [authoring.md](../reference/authoring.md)). Step 1's map
28
31
  is how you verify this — re-run `okf index` and confirm each listing matches
29
32
  reality.
30
33
  5. Run `validate`, then `lint` to catch the curation drift the change introduced —
31
- new orphans, broken citations, dangling index entries. Add `--stale-after`
32
- (e.g. `90d`) if concepts carry timestamps: freshness is off by default, so a
33
- plain `lint` will not tell you what the change left stale.
34
+ new orphans, broken sources, dangling index entries, footnotes that lost
35
+ their source. `expired` reports out of the box; add `--stale-after`
36
+ (e.g. `90d`) when you also want an age cutoff, which stays off by default.
34
37
  6. **Review loose files** <!-- check:unlinked --> — run `okf loose <dir>` (the
35
38
  folder-grouped view of `lint`'s `unlinked` check): the concepts with **no
36
39
  cross-links in or out**, which
@@ -21,7 +21,7 @@ is the lede.
21
21
  text views, which are lighter than `--json` when you are reading a report
22
22
  rather than extracting structure from it. Then recommend by what they
23
23
  report, most-blocking first:
24
- - **`validate` has errors** → lead with **`curate`**: §9 conformance errors are
24
+ - **`validate` has errors** → lead with **`curate`**: §11 conformance errors are
25
25
  the only hard failures, and curate fixes them before anything else.
26
26
  - **clean `validate`, but `lint`/`loose` findings** → lead with **`curate`** to
27
27
  settle the curation debt (reachability, backlog, completeness, hygiene),
@@ -39,9 +39,10 @@ is the lede.
39
39
  with almost no internal traffic in `okf graph --traffic` → offer
40
40
  **`refine`** (evidence-driven restructuring; it proposes before it
41
41
  touches anything).
42
- 4. **Freshness is off by default.** If the bundle carries timestamps, note that a
43
- plain `lint` said nothing about staleness and `okf lint <root> --stale-after
44
- 90d` is the check that would.
42
+ 4. **The age cutoff is off by default.** A plain `lint` already reports concepts
43
+ past their own declared `stale_after` (`expired`), but says nothing about
44
+ *age*; when the bundle records change times, note that `okf lint <root>
45
+ --stale-after 90d` is the check that would.
45
46
 
46
47
  Keep it to two or three pointed picks, each with the exact `/okf:gem <verb>` to
47
48
  run and a one-line reason from the signals. Never auto-run a workflow from here.
@@ -20,23 +20,46 @@ non-trivial migration; its type and tag rules apply here unchanged.
20
20
  block of [templates/concept.md](../templates/concept.md): a small `type`
21
21
  vocabulary derived from what the documents *are* (reuse before minting —
22
22
  check `okf types <dir>` as you go), `title`/`description` from each
23
- document's own heading and purpose line, `timestamp` from the document's own
24
- date when it carries one, `tags` only where connective.
23
+ document's own heading and purpose line, `generated: { by: <actor>, at: … }`
24
+ from the document's own history when it carries one (the actor in §7's
25
+ spelling — never invented), `tags` only where connective.
25
26
  3. **Keep the directory topology** — it is already domain knowledge. Default
26
27
  one file = one concept. When a file shows split signals (two `type`s
27
28
  fighting for the frontmatter, two audiences), flag it for a later `curate`
28
29
  pass; never split, rename, or restructure during migration.
29
30
  4. **Reserved files.** A bundle-root `index.md` from
30
31
  [templates/root-index.md](../templates/root-index.md) (frontmatter is
31
- `okf_version: "0.1"` and nothing else), a nested `index.md` per directory
32
+ `okf_version: "0.2"` and nothing else), a nested `index.md` per directory
32
33
  from [templates/index.md](../templates/index.md), and `log.md` with a dated
33
34
  **Creation** entry naming where the documents came from.
34
35
  5. **Links.** The documents' existing relative links become the graph's edges —
35
36
  verify they resolve inside the bundle and repoint only what a move broke.
36
- Links pointing outside the bundle are tolerated (§5.3); leave them.
37
+ Links pointing outside the bundle are tolerated (§6.1); leave them.
37
38
  6. **Close out** — walk the
38
39
  [Closeout gate](../reference/authoring.md#closeout--the-finishing-gate):
39
- `validate` zero errors, `lint` (pass `--stale-after` when you stamped
40
- timestamps), `loose`, tag review, index eyeball. Then prove the promise:
41
- each concept with its frontmatter block stripped is byte-identical to the
42
- source document.
40
+ `validate` zero errors, `lint`, `loose`, tag review, index eyeball. Then
41
+ prove the promise: each concept with its frontmatter block stripped is
42
+ byte-identical to the source document.
43
+
44
+ ## The other migration: v0.1 spelling → v0.2
45
+
46
+ A bundle already in OKF but written in v0.1's spelling reads correctly forever
47
+ (§13.1), so this pass is convenience — run it when you want the bundle to *write*
48
+ what v0.2 readers expect. Exactly two spellings move, and `lint`'s Migration
49
+ findings name every file carrying either:
50
+
51
+ 1. Run `okf lint <dir>` and read the two Migration findings — `legacy_timestamp`
52
+ and `legacy_citations` list the affected files in their metric.
53
+ 2. **`timestamp` → `generated`.** Move the value under
54
+ `generated: { by: <actor>, at: <the timestamp> }`. The actor is the one thing
55
+ no tool can derive: ask, or write `human:<maintainer>` when the history is
56
+ human — and never let a tool sign a person's name (see
57
+ [produce](produce.md)).
58
+ 3. **`# Citations` → `sources`.** Lift each item into a `sources:` entry —
59
+ a labelled link's text becomes `title`, its URL `resource`; a bare-URL or
60
+ autolink item has no title, so keep `resource` alone or author one. Where
61
+ the body cites a source, add an `id:` to the entry and a `[^id]` footnote at
62
+ the claim. Then delete the section.
63
+ 4. Re-run `okf lint` until the Migration category is clean. The mechanical
64
+ done-check, exit 1 until it is:
65
+ `okf lint <dir> --only legacy_timestamp,legacy_citations --fail-on info`.
@@ -1,24 +1,33 @@
1
1
  # Playbook: produce — create or extend a bundle
2
2
 
3
3
  The craft that makes these steps land well — granularity, choosing `type`, tag
4
- vocabulary, topology, links, citations — lives in
4
+ vocabulary, topology, links, sources — lives in
5
5
  [authoring.md](../reference/authoring.md). Read it before a non-trivial produce.
6
6
 
7
- 1. Read [SPEC.md](../reference/SPEC.md) if you are unsure of any rule.
7
+ 1. Unsure of a rule? [spec-map.md](../reference/spec-map.md) names the § that
8
+ settles it; read that section of [SPEC.md](../reference/SPEC.md).
8
9
  2. Pick the source(s): **code** (derive concepts from source, READMEs, docstrings,
9
- config), **docs/wiki** (distill pages into concepts; cite the originals under
10
- `# Citations`), **manual** (decisions, playbooks, metrics that live only in
11
- people's heads). If the source documents should survive as the concepts
12
- themselves — verbatim — that is [migrate.md](migrate.md), not produce.
10
+ config), **docs/wiki** (distill pages into concepts; record the originals in
11
+ `sources:` and key claims with `[^id]` footnotes), **manual** (decisions,
12
+ playbooks, metrics that live only in people's heads). If the source documents
13
+ should survive as the concepts themselves — verbatim — that is
14
+ [migrate.md](migrate.md), not produce.
13
15
  3. Choose a domain-based directory layout. One concept per file.
14
- 4. Write each concept from [templates/concept.md](../templates/concept.md): a
15
- descriptive `type` from the bundle's vocabulary, recommended fields filled,
16
- cross-links to related concepts written into prose.
16
+ 4. Write each concept from [templates/concept.md](../templates/concept.md) — or
17
+ [templates/attested-computation.md](../templates/attested-computation.md)
18
+ when the concept *is* a sanctioned computation (§10): a descriptive `type`
19
+ from the bundle's vocabulary, recommended fields filled, cross-links to
20
+ related concepts written into prose. `generated.by` says who produced the
21
+ content, in §7's spelling — and when a tool writes a concept unattended, the
22
+ tool writes **its own** identity, `<producer>/<version>`: `human:<maintainer>`
23
+ from a generator would be a fabricated human attestation, the exact false
24
+ provenance §5 exists to prevent.
17
25
  5. Add or refresh `index.md` per directory from
18
26
  [templates/index.md](../templates/index.md); for the bundle root use
19
27
  [templates/root-index.md](../templates/root-index.md) so it carries
20
- `okf_version: "0.1"`. Append a dated entry to `log.md`.
28
+ `okf_version: "0.2"`. Append a dated entry to `log.md`.
21
29
  6. **Close out** — walk the
22
30
  [Closeout gate](../reference/authoring.md#closeout--the-finishing-gate)
23
- (`validate` + `lint` are part of it, see [cli.md](../reference/cli.md))
31
+ (`validate` + `lint` are part of it, see
32
+ [cli/checks.md](../reference/cli/checks.md))
24
33
  before finishing.
@@ -3,7 +3,7 @@
3
3
  Reached by `search <query…>` — the query is everything after the verb; given no
4
4
  query, ask what to find. Retrieval matters as much as curation: a bundle nobody
5
5
  can query cheaply is dead weight. The discipline is progressive disclosure
6
- (spec §6): every step pays a few hundred bytes to decide what the next step
6
+ (spec §8): every step pays a few hundred bytes to decide what the next step
7
7
  reads, and full bodies are read last, and only the winners.
8
8
 
9
9
  1. **Just run it — no presence probe.** Point the finder at a path or an `@slug`
@@ -58,7 +58,7 @@ reads, and full bodies are read last, and only the winners.
58
58
  @slugs (`okf search @handbook @notes <terms>`) or `@all` for every registered
59
59
  one — and read the per-row bundle slug before following an id home.
60
60
  4. **Read only the winners.** A match row's `id` is its file: `<dir>/<id>.md`.
61
- Read that file — not its folder, never the whole tree. Follow its links (§5)
61
+ Read that file — not its folder, never the whole tree. Follow its links (§6)
62
62
  one hop at a time; check `log.md` when freshness matters.
63
63
  5. **Answer, then write back.** Cite the concept ids you used. If the answer
64
64
  was missing, stale, or needlessly hard to find — a gap, a broken link, an