From a5bbf1838824df43b75bea5a327a51e06f827208 Mon Sep 17 00:00:00 2001 From: MartinForReal Date: Fri, 31 Jul 2026 16:57:48 +0800 Subject: [PATCH 1/5] Flag a stale_after date that has already passed (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isStale derived the answer but nothing called it, so validation only checked the date's form: a deadline from last year passed silently. Under Validate trust & lifecycle fields, a note past its stale_after now says it is due for review. Advisory only — §11 keeps it out of conformance. --- validator.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/validator.ts b/validator.ts index b1e159c..a0a5df1 100755 --- a/validator.ts +++ b/validator.ts @@ -1168,7 +1168,7 @@ function validateTrustFamilies(data: Record): OkfIssue[] { } } - // `stale_after` (§5.5): an absolute YYYY-MM-DD date. + // `stale_after` (§5.5): an absolute YYYY-MM-DD date, and a review deadline. if ("stale_after" in data && data["stale_after"] != null) { const s = String(data["stale_after"]).slice(0, 10); if (!ISO_DATE_RE.test(s)) { @@ -1177,6 +1177,12 @@ function validateTrustFamilies(data: Record): OkfIssue[] { rule: "§5.5", message: "`stale_after` should be an absolute date (`YYYY-MM-DD`).", }); + } else if (isStale(data)) { + issues.push({ + severity: "warning", + rule: "§5.5", + message: `\`stale_after\` (${s}) has passed; this concept is due for review.`, + }); } } From 4daf04ec4ea29bcd4bd75ba19a5cf66e27cd10d5 Mon Sep 17 00:00:00 2001 From: MartinForReal Date: Fri, 31 Jul 2026 16:58:04 +0800 Subject: [PATCH 2/5] Check sources[].author, which the docs said we already did (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README and the 0.3.0 notes both listed `author` among the §5.1 credibility signals this plugin validates, but the loop only looked at `resource`, `usage_count` and `last_modified`. Warn when an `author` is present and isn't a non-empty string, matching its neighbours. --- validator.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/validator.ts b/validator.ts index a0a5df1..c5defd2 100755 --- a/validator.ts +++ b/validator.ts @@ -1213,6 +1213,16 @@ function validateTrustFamilies(data: Record): OkfIssue[] { message: `\`sources[${i}]\` is missing the required \`resource\`.`, }); } + if ( + "author" in e && + (typeof e["author"] !== "string" || e["author"].trim().length === 0) + ) { + issues.push({ + severity: "warning", + rule: "§5.1", + message: `\`sources[${i}].author\` should be a non-empty string.`, + }); + } if ("usage_count" in e && typeof e["usage_count"] !== "number") { issues.push({ severity: "warning", From 2a5314da0a635e5ac2c2c10d7313f07eeb8cd91e Mon Sep 17 00:00:00 2001 From: MartinForReal Date: Fri, 31 Jul 2026 16:58:23 +0800 Subject: [PATCH 3/5] Show the derived trust tier in the status-bar tooltip (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trustTier computed unverified / machine-confirmed / human-reviewed from `verified` (§5.3), but nothing called it, so the tier reached no surface even though the README advertised it. The status bar is the right home: the report pane lists only files that have issues, so a conformant note — the case where the tier is most worth knowing — would never appear there. Shown only when Validate trust & lifecycle fields is on, since that is the toggle that governs §5. trustTierOfContent wraps the parse so main.ts doesn't have to reach for parseYaml itself. --- main.ts | 31 ++++++++++++++++++++++--------- validator.ts | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/main.ts b/main.ts index f45ef51..b16143a 100755 --- a/main.ts +++ b/main.ts @@ -16,6 +16,7 @@ import { DEFAULT_SETTINGS, validateContent, applyFixes, + trustTierOfContent, isReserved, isExcluded, basename, @@ -409,7 +410,7 @@ export default class OkfPlugin extends Plugin { this.isRoot(file), this.settings ); - this.updateStatus(postIssues); + this.updateStatus(postIssues, content); const remainingErrors = postIssues.filter((i) => i.severity === "error"); if (remainingErrors.length > 0) { @@ -437,7 +438,7 @@ export default class OkfPlugin extends Plugin { this.isRoot(file), this.settings ); - this.updateStatus(issues); + this.updateStatus(issues, content); if (openReport) { this.renderResults(issues.length ? [{ path: file.path, issues }] : [], 1); void this.activateView(); @@ -445,9 +446,16 @@ export default class OkfPlugin extends Plugin { } } - private updateStatus(issues: OkfIssue[]) { + private updateStatus(issues: OkfIssue[], content?: string) { const errs = issues.filter((i) => i.severity === "error").length; const warns = issues.filter((i) => i.severity === "warning").length; + // The derived trust tier (§5.3) describes the note rather than faulting it, + // so it rides in the tooltip instead of the issue list. The report pane only + // lists files that have issues, which would hide the tier on a clean note. + const tier = + this.settings.warnTrustFields && content !== undefined + ? trustTierOfContent(content) + : null; this.statusEl.removeClass( "okf-statusbar-ok", "okf-statusbar-bad", @@ -465,15 +473,20 @@ export default class OkfPlugin extends Plugin { } // Tooltip carries the detail so we don't need a Notice for routine checks. if (issues.length === 0) { - this.statusEl.setAttribute( - "aria-label", - "Active note conforms to OKF v0.2 — click to scan the vault" - ); + const lines = ["Active note conforms to OKF v0.2"]; + if (tier) lines.push(`Trust tier: ${tier}`); + lines.push(""); + lines.push("Click to scan the whole vault"); + this.statusEl.setAttribute("aria-label", lines.join("\n")); } else { const lines = issues .slice(0, 8) .map((i) => `${i.severity === "error" ? "✖" : "⚠"} ${i.rule} ${i.message}`); if (issues.length > 8) lines.push(`…and ${issues.length - 8} more`); + if (tier) { + lines.push(""); + lines.push(`Trust tier: ${tier}`); + } lines.push(""); lines.push("Click to scan the whole vault"); this.statusEl.setAttribute("aria-label", lines.join("\n")); @@ -627,7 +640,7 @@ export default class OkfPlugin extends Plugin { this.isRoot(file), this.settings ); - this.updateStatus(issues); + this.updateStatus(issues, content); } async fixAll() { @@ -1158,7 +1171,7 @@ class OkfSettingTab extends PluginSettingTab { }, { name: "Validate trust & lifecycle fields", - desc: "Check the v0.2 trust fields on notes that carry them: `verified`, `status`, `stale_after`, `sources` (§5). Advisory; off by default.", + desc: "Check the v0.2 trust fields on notes that carry them: `verified`, `status`, `stale_after` (including whether it has passed), `sources` (§5), and show the note's trust tier in the status-bar tooltip. Advisory; off by default.", control: (row) => row.addToggle((tg) => tg.setValue(s.warnTrustFields).onChange((v) => { diff --git a/validator.ts b/validator.ts index c5defd2..d838559 100755 --- a/validator.ts +++ b/validator.ts @@ -167,6 +167,28 @@ export function trustTier( return "machine-confirmed"; } +/** + * Trust tier of a note's frontmatter, or null when it has none we can read. + * A convenience for callers holding raw file content rather than parsed + * frontmatter; unparseable YAML is reported by `validateContent`, so here it + * just means "no tier to show". + */ +export function trustTierOfContent( + content: string +): "unverified" | "machine-confirmed" | "human-reviewed" | null { + const { hasFm, raw } = splitFrontmatter(content); + if (!hasFm) return null; + try { + const parsed: unknown = parseYaml(raw); + if (parsed && typeof parsed === "object") { + return trustTier(parsed as Record); + } + } catch { + return null; + } + return null; +} + /** * Whether a concept is stale per `stale_after` (§5.5): stale when * `today >= stale_after`. False when absent or unparseable. From f6c26b71869b5f61bfab9b5343c1bda147efe018 Mon Sep 17 00:00:00 2001 From: MartinForReal Date: Fri, 31 Jul 2026 16:58:40 +0800 Subject: [PATCH 4/5] Say only what the trust checks actually do (#10, #11, #12) The README's feature list and the 0.3.0 changelog entry described derived trust tiers and `sources[].author` validation that hadn't shipped. The three commits before this one make those claims true; this one brings the prose in line and records the correction against the 0.3.0 entry, so anyone reading the old notes isn't misled. --- CHANGELOG.md | 40 +++++++++++++++++++++++++++---- README.md | 2 +- main.js | 67 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d577f7..b36b448 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +Three §5 trust signals that the 0.3.0 notes and README described as shipped were +either not surfaced or not checked. All three now do what the docs said. + +### Added +- **`stale_after` is enforced, not just parsed** ([#10](https://github.com/MartinForReal/okf-enforcer/issues/10)). + A note whose `stale_after` has passed is flagged as due for review; previously + only the date's *form* was checked, so a deadline from last year passed + silently. Advisory only — §11 keeps it out of conformance. +- **The derived trust tier is shown** ([#11](https://github.com/MartinForReal/okf-enforcer/issues/11)). + The active note's tier (unverified / machine-confirmed / human-reviewed, §5.3) + appears in the status-bar tooltip. It was computed but reached no surface. The + report pane lists only files that *have* issues, so a conformant note would + never show a tier there. +- **`sources[].author` is validated** ([#12](https://github.com/MartinForReal/okf-enforcer/issues/12)). + Warns when an entry's `author` is present but not a non-empty string, + alongside the existing `resource` / `usage_count` / `last_modified` checks. + +All three sit behind **Validate trust & lifecycle fields**, off by default, so no +vault starts reporting anything new unless it had already opted in. + +### Fixed +- README and the 0.3.0 changelog entry claimed derived trust tiers and + `sources[].author` validation that had not shipped. Corrected. + ## [0.4.1] - 2026-07-31 ### Changed @@ -254,10 +280,16 @@ the fallbacks below, so existing vaults keep validating. ### Added - **Provenance / trust / lifecycle validation** (§5), opt-in via **Validate trust & lifecycle fields**. When present, checks `generated`/`verified` shape and the - actor convention (`/`, `human:`, `process:`), derives - trust tiers (unverified / machine-confirmed / human-reviewed), and validates - `status` (`draft|stable|deprecated`), `stale_after` (absolute date), and - `sources` with its credibility signals (`author`/`usage_count`/`last_modified`). + actor convention (`/`, `human:`, `process:`), and + validates `status` (`draft|stable|deprecated`), `stale_after` (absolute date), + and `sources` (`resource`, `usage_count`, `last_modified`). + + > **Correction.** This entry originally read "derives trust tiers (unverified / + > machine-confirmed / human-reviewed)" and listed `author` among the validated + > `sources` signals. Neither was true of 0.3.0: the tier was computed but never + > shown anywhere, and `author` was not checked. Both are fixed in Unreleased + > ([#11](https://github.com/MartinForReal/okf-enforcer/issues/11), + > [#12](https://github.com/MartinForReal/okf-enforcer/issues/12)). - **Attested Computation concepts** (§10). A `type: Attested Computation` note is checked for a required `runtime`, a present computation (inline `# Computation` fence or a `computation` path), and `parameters`/`executor`/`attester` shape. diff --git a/README.md b/README.md index 759777f..93f08ef 100755 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ OKF is an open, minimal convention for representing knowledge as a directory of ## Features - **Conformance validation.** Checks every note against OKF v0.2. The hard rules (parseable frontmatter, non-empty `type`, valid `index.md`/`log.md` structure — §11/§8/§9) are reported as **errors**; the spec's recommended fields and SHOULD-guidance are **warnings** you can toggle. The spec's permissive rules are respected — broken links and missing optional fields never fail a bundle. -- **Trust & provenance (v0.2).** Opt-in checks for the §5 families: `generated`/`verified` and the actor convention, derived trust tiers (unverified / machine-confirmed / human-reviewed), `status` (`draft|stable|deprecated`), `stale_after`, and `sources` with its credibility signals. +- **Trust & provenance (v0.2).** Opt-in checks for the §5 families: `generated`/`verified` and the actor convention, `status` (`draft|stable|deprecated`), `stale_after` — both that it's an absolute date and whether it has passed, so a note that's due for review says so — and `sources` with its credibility signals (`author`/`usage_count`/`last_modified`). The note's derived trust tier (unverified / machine-confirmed / human-reviewed) is shown in the status-bar tooltip. - **Attested Computation (v0.2).** Validates `type: Attested Computation` concepts (§10) — a required `runtime`, a present computation (inline `# Computation` fence or a `computation` path), and `parameters`/`executor`/`attester` shape. - **v0.1 → v0.2 migration.** A command rewrites a legacy `timestamp` into `generated: { by, at }` and lifts a body `# Citations` list into `sources`, then offers to declare `okf_version: "0.2"` in the root index. - **Vault-wide report.** A compact, collapsible side panel lists every non-conformant note, errors first, with a one-line summary of conformant / error / warning counts. Hidden by default; opens only on demand. diff --git a/main.js b/main.js index 4a440ae..0c12233 100755 --- a/main.js +++ b/main.js @@ -190,6 +190,36 @@ function normalizeVerified(data) { if (typeof v === "object") return [v]; return []; } +function trustTier(data) { + var _a; + const events = normalizeVerified(data); + if (events.length === 0) return "unverified"; + for (const e of events) { + if (String((_a = e["by"]) != null ? _a : "").startsWith("human:")) return "human-reviewed"; + } + return "machine-confirmed"; +} +function trustTierOfContent(content) { + const { hasFm, raw } = splitFrontmatter(content); + if (!hasFm) return null; + try { + const parsed = (0, import_obsidian.parseYaml)(raw); + if (parsed && typeof parsed === "object") { + return trustTier(parsed); + } + } catch (e) { + return null; + } + return null; +} +function isStale(data, today = /* @__PURE__ */ new Date()) { + const raw = data["stale_after"]; + if (!raw) return false; + const s = String(raw).slice(0, 10); + if (!ISO_DATE_RE.test(s)) return false; + const t = today.toISOString().slice(0, 10); + return t >= s; +} function basename(path) { const f = path.split("/").pop() || path; return f.replace(/\.md$/i, ""); @@ -779,6 +809,12 @@ function validateTrustFamilies(data) { rule: "\xA75.5", message: "`stale_after` should be an absolute date (`YYYY-MM-DD`)." }); + } else if (isStale(data)) { + issues.push({ + severity: "warning", + rule: "\xA75.5", + message: `\`stale_after\` (${s}) has passed; this concept is due for review.` + }); } } if ("sources" in data && data["sources"] != null) { @@ -807,6 +843,13 @@ function validateTrustFamilies(data) { message: `\`sources[${i}]\` is missing the required \`resource\`.` }); } + if ("author" in e && (typeof e["author"] !== "string" || e["author"].trim().length === 0)) { + issues.push({ + severity: "warning", + rule: "\xA75.1", + message: `\`sources[${i}].author\` should be a non-empty string.` + }); + } if ("usage_count" in e && typeof e["usage_count"] !== "number") { issues.push({ severity: "warning", @@ -1577,7 +1620,7 @@ var OkfPlugin = class extends import_obsidian3.Plugin { this.isRoot(file), this.settings ); - this.updateStatus(postIssues); + this.updateStatus(postIssues, content); const remainingErrors = postIssues.filter((i) => i.severity === "error"); if (remainingErrors.length > 0) { this.promptForRequiredFields(file, remainingErrors); @@ -1599,16 +1642,17 @@ var OkfPlugin = class extends import_obsidian3.Plugin { this.isRoot(file), this.settings ); - this.updateStatus(issues); + this.updateStatus(issues, content); if (openReport) { this.renderResults(issues.length ? [{ path: file.path, issues }] : [], 1); void this.activateView(); if (!issues.length) new import_obsidian3.Notice("OKF: active note is conformant \u2705"); } } - updateStatus(issues) { + updateStatus(issues, content) { const errs = issues.filter((i) => i.severity === "error").length; const warns = issues.filter((i) => i.severity === "warning").length; + const tier = this.settings.warnTrustFields && content !== void 0 ? trustTierOfContent(content) : null; this.statusEl.removeClass( "okf-statusbar-ok", "okf-statusbar-bad", @@ -1625,13 +1669,18 @@ var OkfPlugin = class extends import_obsidian3.Plugin { this.statusEl.addClass("okf-statusbar-ok"); } if (issues.length === 0) { - this.statusEl.setAttribute( - "aria-label", - "Active note conforms to OKF v0.2 \u2014 click to scan the vault" - ); + const lines = ["Active note conforms to OKF v0.2"]; + if (tier) lines.push(`Trust tier: ${tier}`); + lines.push(""); + lines.push("Click to scan the whole vault"); + this.statusEl.setAttribute("aria-label", lines.join("\n")); } else { const lines = issues.slice(0, 8).map((i) => `${i.severity === "error" ? "\u2716" : "\u26A0"} ${i.rule} ${i.message}`); if (issues.length > 8) lines.push(`\u2026and ${issues.length - 8} more`); + if (tier) { + lines.push(""); + lines.push(`Trust tier: ${tier}`); + } lines.push(""); lines.push("Click to scan the whole vault"); this.statusEl.setAttribute("aria-label", lines.join("\n")); @@ -1781,7 +1830,7 @@ Click to open the report` this.isRoot(file), this.settings ); - this.updateStatus(issues); + this.updateStatus(issues, content); } async fixAll() { if (this.busy) { @@ -2193,7 +2242,7 @@ var OkfSettingTab = class extends import_obsidian3.PluginSettingTab { }, { name: "Validate trust & lifecycle fields", - desc: "Check the v0.2 trust fields on notes that carry them: `verified`, `status`, `stale_after`, `sources` (\xA75). Advisory; off by default.", + desc: "Check the v0.2 trust fields on notes that carry them: `verified`, `status`, `stale_after` (including whether it has passed), `sources` (\xA75), and show the note's trust tier in the status-bar tooltip. Advisory; off by default.", control: (row) => row.addToggle( (tg) => tg.setValue(s.warnTrustFields).onChange((v) => { s.warnTrustFields = v; From 820c7245db1a0b640d155d1b1825cabb86345cc9 Mon Sep 17 00:00:00 2001 From: MartinForReal Date: Fri, 31 Jul 2026 17:40:33 +0800 Subject: [PATCH 5/5] Finish the docs pass for the trust fixes (#10, #11, #12) The earlier commit corrected the two claims that were outright wrong. This covers what was left: - The Unreleased blurb lumped all three gaps together as overclaimed docs. Only two were: 0.3.0 accurately described `stale_after` as an absolute-date check, and the gap there was that form was all it ever checked. Say that instead. - README's status-bar and Rules entries didn't mention the tier at all. A validation toggle changing what a tooltip shows is surprising enough to say out loud in Settings, not just in Features. - Add the missing changelog link definitions. Unreleased, 0.4.1, and all of 0.2.x had headings with no matching reference, so they rendered as bare `[0.4.1]` text. 0.1.0 and 0.1.1 are tags with no release, but GitHub serves a tag page for those, so the same URL shape works throughout. --- CHANGELOG.md | 11 +++++++++-- README.md | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b36b448..35fb820 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -Three §5 trust signals that the 0.3.0 notes and README described as shipped were -either not surfaced or not checked. All three now do what the docs said. +Three §5 trust signals that didn't do what they promised. The derived trust tier +and `sources[].author` were described as shipped in the 0.3.0 notes and the +README but were never surfaced or checked; `stale_after` was validated for its +form only, so a review deadline could pass unnoticed. ### Added - **`stale_after` is enforced, not just parsed** ([#10](https://github.com/MartinForReal/okf-enforcer/issues/10)). @@ -440,8 +442,13 @@ Initial release. - Batched, non-blocking scan/fix queue with an inline progress bar for large vaults. - Settings for automation toggles, batch size, warning rules, and excluded folders. +[Unreleased]: https://github.com/MartinForReal/okf-enforcer/compare/0.4.1...HEAD +[0.4.1]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.4.1 [0.4.0]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.4.0 [0.3.0]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.3.0 +[0.2.2]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.2.2 +[0.2.1]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.2.1 +[0.2.0]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.2.0 [0.1.3]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.1.3 [0.1.2]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.1.2 [0.1.1]: https://github.com/MartinForReal/okf-enforcer/releases/tag/0.1.1 diff --git a/README.md b/README.md index 93f08ef..c3f0f2a 100755 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ OKF is an open, minimal convention for representing knowledge as a directory of - **Attested Computation (v0.2).** Validates `type: Attested Computation` concepts (§10) — a required `runtime`, a present computation (inline `# Computation` fence or a `computation` path), and `parameters`/`executor`/`attester` shape. - **v0.1 → v0.2 migration.** A command rewrites a legacy `timestamp` into `generated: { by, at }` and lifts a body `# Citations` list into `sources`, then offers to declare `okf_version: "0.2"` in the root index. - **Vault-wide report.** A compact, collapsible side panel lists every non-conformant note, errors first, with a one-line summary of conformant / error / warning counts. Hidden by default; opens only on demand. -- **Status-bar indicator.** A single status-bar item shows the active note's state (`✓` / `⚠` / `✖`) with details in its tooltip. Click it to auto-fix the current note. +- **Status-bar indicator.** A single status-bar item shows the active note's state (`✓` / `⚠` / `✖`) with details in its tooltip — including the note's derived trust tier, when trust checks are on. Click it to auto-fix the current note. - **Auto-fix.** Inserts missing frontmatter (`type`, `title`, `generated`) non-destructively — it never overwrites values you've set. - **Prompt for required fields.** When a note is missing a meaningful `type`, a dialog lets you set `type`, `title`, and `description` directly. - **On-save & on-create hooks.** New notes, edited notes, and notes added by the **Importer** plugin are brought into conformance automatically. @@ -53,7 +53,7 @@ Configure under **Settings → OKF Enforcer**: - **Generate index.md on startup** — off by default, since the hooks above already keep listings current while the plugin is running. Turn it on to bring every folder's `index.md` up to date once when the plugin loads, for what changed while Obsidian was closed — a vault synced from another machine, or edited outside it. It runs before the startup scan, so the scan judges the listings as they now stand rather than the ones it is about to replace, and it runs quietly: progress shows in the status bar, with no notice at the end. - **Rebuild existing index.md** — off by default, so generating over an existing index adds what's missing, fixes links that point at the wrong path, and removes entries for notes that are gone, changing nothing else. Turn it on to rewrite the listing from the folder's contents instead, which also refreshes every description, re-sorts the entries, and re-groups them under their current `type` — at the cost of any prose in the file, which a rebuild discards apart from the section named below. - **Subdirectory description section** — heading in a subfolder's `index.md` whose first paragraph becomes that folder's description in the parent listing (e.g. `Purpose`). Blank by default, which leaves subdirectory entries undescribed. Additive generation never touches the file's prose, and this is the one section a rebuild carries over, so the description you write survives a refresh either way. -- **Rules** — **Warn on missing recommended fields**, **Warn on missing tags**, **Validate trust & lifecycle fields**, and **Validate Attested Computation concepts** select which optional checks to surface. `index.md` / `log.md` structure is always checked: §11 makes it one of the three requirements for a conformant bundle, so it isn't a toggle. +- **Rules** — **Warn on missing recommended fields**, **Warn on missing tags**, **Validate trust & lifecycle fields**, and **Validate Attested Computation concepts** select which optional checks to surface. **Validate trust & lifecycle fields** also decides whether the note's trust tier appears in the status-bar tooltip, since the tier is derived from the same §5 `verified` events the checks read. `index.md` / `log.md` structure is always checked: §11 makes it one of the three requirements for a conformant bundle, so it isn't a toggle. - **Excluded folders** — paths skipped during validation and index generation (default: `Templates`). Use this for attachment folders you'd rather not have an `index.md` in. The Obsidian config folder (e.g. `.obsidian`) is always skipped automatically. - **Batch size** — files processed per async chunk (lower = smoother UI on very large vaults). - **Enable Portent validation** _(experimental / beta — the Portent spec is pre-1.0 and may change)_ — layer the [Portent](https://portent.md) spec on top of OKF: default type vocabulary (`Project`, `Operation`, `Responsibility`, `Task`, `Event`, `Note`, `Topic`, `Person`), lifecycle metadata (optional and format-free — a single `status`/`state` value, boolean `organized`/`archived`, or omitted entirely when organized by default), and relationship shape (`belongs_to` single wikilink, `related_to` list of wikilinks). All Portent findings are warnings — they never break OKF conformance.