From c6e2a1daca7eeda0cd93f8d2f7e2128cad1bab6b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 21 Oct 2026 02:16:33 -0700 Subject: [PATCH] docs(log): the work log stops re-teaching what the concept pages teach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 504 lines, 4,496 words — the log had become the largest page in the bundle, and the reason was not history: twelve entries dated 2026-10-18 → 2026-10-21 each re-narrated the incident AND re-stated the law AND pointed at the page that owns the law. Three copies of one fact, dated three ways. 384 lines / 3,251 words now. What was cut: the restatements. The refuse-by-default guard belongs to `tools/pcie-probe.md`, the completion-sentinel law to `tools/remote-job.md`, the verbatim mode-string law to `tools/niri/displays.md`, the enable-snapshots-a-path trap to `tools/dotfiles-deploy.md`, the sorted-directive rule to `caddy/caddyfile.md`, the effort-map contract to `pi/models.md`, the tier law and the footer/`xdg-open` fleet laws to `AGENTS.md`, and the gate's mechanics to `.githooks/pre-commit` plus its tests — each now named once, from a short entry. What was kept deliberately: laws with no other owner anywhere in the bundle. Fail-closed on a blind `git diff`; a stub that echoes its environment instead of its argument cannot see the bug; a name is data, never let the shell expand it; extensions must never await work that can exceed 30 s; `systemctl --user enable` snapshots an absolute path; agents never run sudo/yay/pacman/pkexec (faillock incident, with the reset command); a claim about what the harness sends is worth exactly its last capture; a number in a page is a claim, not a measurement. Deleting those would have been the actual loss. The 2025-08-05 entry is annotated rather than deleted: this repo's history dates that file's arrival at 2026-08-28 (the OKF rebuild copied it), so 2025-08-05 is a legacy-tree date and the entry now says so instead of silently contradicting `git log`. Refs #64 --- omp-docs/log.md | 608 +++++++++++++++++++----------------------------- 1 file changed, 244 insertions(+), 364 deletions(-) diff --git a/omp-docs/log.md b/omp-docs/log.md index dcf5b87..933debf 100644 --- a/omp-docs/log.md +++ b/omp-docs/log.md @@ -17,488 +17,368 @@ lesson, a few lines. Full incident narratives are recoverable from git on its concept page, linked here. Never re-expand an entry in place. This is the bundle's only work log; [references/okf-spec.md](references/okf-spec.md) owns the -one-log rule and `scripts/kb-lint.sh` enforces it. +one-log rule and `scripts/kb-lint.sh` enforces it. A law that a concept page already owns is +not restated here — entry, then pointer. That is what the 2026-10-21 audit pass did to the +newest twelve entries: the facts stayed, the duplicates went, `git log --follow` keeps the +long form. ## 2026-10-21 — reviewing a day of work, and finding checks that lie -Two-axis review (Standards / Spec) of everything landed 2026-10-20 → 2026-10-21: 51 commits, -30 findings, worked on `fix/60-review-findings` (issue #60). Three findings were the same shape — -a check that reports confidence it did not earn: +Two-axis review (Standards / Spec) of 51 commits, 30 findings, on `fix/60-review-findings`. +Refs #60, merged PR #61. The findings were one shape: **a check that reports confidence it did +not earn.** -- **A guard that cannot see must refuse.** `pcie-probe.sh` dropped the `/dev/kfd` half of its - busy check when `fuser` was absent, and `gpu_busy_percent` reads `0` for a resident-but-idle - service: the tool measured beside a live endpoint while its own LAW said not to. Missing - visibility is now a refusal (exit `3`), not a narrower check. -- **A swallowed status is a false report.** `deploy.sh` printed `ENABLE` for units systemd - refuses to enable (`live-sync.service` has no `[Install]` section; its timer arms it), and the - loop walked `*.service` only, so a rebuilt box deployed the heartbeat and never armed its - timer. Every outcome now reports what actually happened. -- **A gate that reads a quoted path reads nothing.** git quotes non-ASCII paths in - `--name-only` output; the quoted name came back as an empty diff and the secret gate - `continue`d past it. Fail closed means `-z`, and it means the placeholder filter reads the - assigned value — a comment saying `# example` cannot vouch for the token beside it. +- A guard that loses half its condition when a tool is missing must refuse, not narrow — + `pcie-probe.sh` measured beside a live endpoint while its own LAW said not to + ([tools/pcie-probe.md](tools/pcie-probe.md)). +- A deployer that prints `ENABLE` for a unit systemd refuses to enable (`live-sync.service` has + no `[Install]`; its timer arms it) reports a belief, not an outcome, and walking `*.service` + only left the heartbeat unarmed on a rebuilt box + ([tools/dotfiles-deploy.md](tools/dotfiles-deploy.md)). +- A gate handed git's quoted path reads an empty diff and passes. Fail closed means `-z` + (`.githooks/pre-commit`, `tests/secret-gate.bats`). -The law that cost the most, from the probe: **a test that observes the tool's environment instead -of what the tool passed on cannot see the bug.** The stub probe echoed `$HIP_VISIBLE_DEVICES` -rather than its argument, so the suite asserted the mask was set and per-card bandwidth was -unobtainable on the 2×R9700 box. Model the platform rule in the stub and the existing test goes -red against the old code — that is the red-proof worth having. +Two laws no other page owns: **a stub that echoes its environment instead of its argument +cannot see the bug** — the probe suite asserted the mask was set and never read what the tool +passed, so per-card bandwidth stayed unobtainable; and **a name is data — split it, never let +the shell expand it** (`remote-job.sh status` globbed its own arguments, so job `x*` was +reported as whatever matched in the caller's directory: [tools/remote-job.md](tools/remote-job.md)). -Same shape in `remote-job.sh status`: it globbed its arguments, so a job named `x*` was reported -as whatever happened to match in the caller's directory. A name is data — split it (`read -a`), -never let the shell expand it — and a multiplexed `tail` with no jobs says "no jobs" instead of -handing `*.out` to tail and reporting a missing file. +The heartbeat's three missed ticks were this gate firing on a managed skill that documents the +vendor's all-zeros placeholder id: prose about credentials *is* the machine tier, and a gate +that blocks it gets worked around — the workaround is the hole. Placeholder judgement reads the +matched token and what follows it (a fixed quantifier truncates `EXAMPLE` to `EXA`), never the +whole line. `ALLOW_SECRETS` stays the only override. -Incident, found while verifying the gate: the live-sync heartbeat had failed three hourly ticks, -because a managed skill that documents how to write a secret gate quotes the vendor's all-zeros -placeholder id and the gate's shape rule fired on the documentation. Prose about credentials is -what the machine tier is made of; a gate that blocks it is worked around, and the workaround is -the hole. Placeholder judgement reads the matched token — plus what follows it, because a fixed -quantifier truncates `EXAMPLE` to `EXA` — never the whole line. `ALLOW_SECRETS` stays the only -override, and the heartbeat's own payload was replayed through the fixed hook to prove it lands. - -The rest of #60 was claims about the machine that the machine had stopped honouring: -`.gitignore` and `deploy.sh`'s exclusion list had drifted apart (`Noctalia*` was excluded, never -ignored), `.gitattributes` was the stated reason `git-lfs` was installed — for wallpapers that -same week's diff untracked, and the one-log rule was restated in four owners. A coverage claim -names what was measured; a tool justified by dead config is a tool nobody can defend when the -config goes. +The rest of #60 was claims the machine had stopped honouring: `.gitignore` and `deploy.sh`'s +exclusion list drifted apart, `.gitattributes` justified installing `git-lfs` for wallpapers +that same week's diff untracked, and the one-log rule had four owners. A coverage claim names +what was measured; a tool justified by dead config cannot survive the config. ## 2026-10-21 — an effort map folding against rejections that never existed -`agent/models.yml` rewrote `minimal→low`, `high→xhigh` and `max→xhigh` for -frank-dragoon to dodge 400s the engine does not send: flash-server's own error names -its allow-list (`minimal|low|medium|high|xhigh|max|none`) and the harness passes an -unmapped level through verbatim, so the fold only asked for a different thinking -budget than the caller named. The map is identity now, and -`reasoningDisableMode: none-effort` was added because the default disable mode does -not disable — `:off` left as `reasoning_effort: "minimal"` and paid for a canned -thinking block, where `none` returns `reasoning_tokens: 0`. Both settled by capturing -the wire through a logging proxy rather than reading the config: a claim about what -the harness sends is worth exactly its last capture. The same pass restored the three -pre-callsign model ids (`frank-medium` / `frank-large` / `frank-small`) that the -2026-10-08 rename had overwritten inside its own explanatory comments. Refs #62 +Refs #62. `agent/models.yml` rewrote `minimal→low`, `high→xhigh`, `max→xhigh` for frank-dragoon +to dodge 400s the engine does not send — flash-server's own error names its allow-list and the +harness passes an unmapped level through verbatim, so the fold only asked for a different +thinking budget than the caller named. The map is identity now, and +`reasoningDisableMode: none-effort` was added because the default disable mode does not disable: +`:off` still sends `reasoning_effort: "minimal"` and pays for a canned thinking block. Contract +owner: [pi/models.md](pi/models.md). Law: **a claim about what the harness sends is worth +exactly its last capture** — settle it through a logging proxy, not by reading config. The same +pass restored the three pre-callsign model ids the 2026-10-08 rename had overwritten inside its +own explanatory comments. ## 2026-10-21 — a documented mode the panel never had -The home ultrawide had been running at 60 Hz for weeks while `desk-1-home.kdl` -asked for `3440x1440@239.999`. The page that owns that file documented the same -phantom mode, so every reader — including tonight's inspection — trusted it. -The panel does offer a 240-class mode: `niri msg outputs` lists `3440x1440@239.998`. The defect -was one character — niri matches mode strings verbatim, and `239.999` is not `239.998`. - -Laws, now on [tools/niri/displays](tools/niri/displays.md): - -- **A mode string must match the advertised string verbatim.** niri does not round: - `3440x1440@239.999` does not select `3440x1440@239.998`. It logs - `configured mode … could not be found, falling back to preferred` and drives the EDID - preferred mode — 60 Hz here. A one-character typo therefore looks like a dying monitor, and - every connector drop re-enacts it. -- **Three layers, three signatures:** config fallback, DP link drops, and - amdgpu `DMCUB` errors with `EINVAL` page flips after s2idle. Only the first - yields to a file edit; only the last yields to a reboot. Triage table and - probes are on the page. -- **A number in a page is a claim, not a measurement.** Mode strings, refresh rates and tool - availability were wrong on several pages at once — `wlr-randr` was listed as installed while - `pacman -Qs wlr` matches nothing. Read the hardware before writing a mode into a runbook, and - destroy the stale copies. A correction is itself a measurement: the first pass at this entry - published a second wrong hardware fact, with a `verified:` date stamped on it. - -Fix: `desk-1-home.kdl` 239.999 → 180.000 on the identity block and both legacy -connector blocks, applied live with `niri msg output mode ` — the -temporary per-output path, so no config reload and no logged-out session. +The home ultrawide ran at 60 Hz for weeks because `desk-1-home.kdl` asked for +`3440x1440@239.999` and the panel advertises `@239.998`. One character: niri matches mode +strings verbatim, does not round, and a mode it cannot find does not fail — it falls back to the +EDID preferred mode, 60 Hz, on every connector drop. The page that owns the file documented the +same phantom mode, so every reader trusted it. Laws, triage table and probes: +[tools/niri/displays.md](tools/niri/displays.md). Durable: **a number in a page is a claim, not +a measurement** — mode strings, refresh rates and tool availability were wrong on several pages +at once (`wlr-randr` listed as installed while `pacman -Qs wlr` matches nothing). Read the +hardware, then destroy the stale copies. A correction is itself a measurement: this entry's +first pass published a second wrong hardware fact, with a `verified:` date stamped on it. ## 2026-10-20 — one work log per bundle -The bundle had two: this page and `core/log.md`, created "for tracking core -directory modifications" — two entries of pure migration bookkeeping (file moved, -directory created, log created), and a date for the OKF conversion that neither this -page nor git agrees with: 2026-07-15 there, 2026-01-15 here, while `git log` dates the -OKF rebuild 2026-08-28. `core/index.md` was still pointing readers at it, which is how -a stale page stays alive. Trashed to `omp-docs/core/.AGENTTRASH/log.md` — its content -is recoverable from git, which is the entire argument against a second log. - -Laws, now written down in [references/okf-spec.md](references/okf-spec.md) and -[references/okf-maintenance.md](references/okf-maintenance.md), and gated: a bundle has -ONE work log, `log.md`, at its root, and `scripts/kb-lint.sh` reports any `log.md` that -is not the root's — so the sprawl that made logging useless once cannot restart -quietly. The agent-side duty is in `agent/APPEND_SYSTEM.md` § 1: log here, read here, -never start another. +The bundle had two: this page and `core/log.md` — two entries of pure migration bookkeeping and +a conversion date that neither this page nor git agreed with (2026-07-15 there, 2026-01-15 here, +`git log` dates the OKF rebuild 2026-08-28); `core/index.md` still pointed readers at it, which +is how a stale page stays alive. Trashed to `omp-docs/core/.AGENTTRASH/`. The rule lives in +[references/okf-spec.md](references/okf-spec.md) and +[references/okf-maintenance.md](references/okf-maintenance.md) and is gated by +`scripts/kb-lint.sh`; the agent-side duty is `agent/APPEND_SYSTEM.md` § 1. ## 2026-10-20 — two instruments rescued from `$HOME` script sediment -`$HOME` had accumulated 123 loose scripts at its root (archived to -`.AGENTTRASH/home-scripts-2026-10-20`, ~1.8 GiB of stray audio consolidated into -`~/audiosamples`). Two were durable instruments and now live in the repo with -black-box tests and pages: [tools/remote-job](tools/remote-job.md) and -[tools/pcie-probe](tools/pcie-probe.md). Refs #53. Laws learned: - -- **A readiness pattern in a log is not evidence of completion.** `ssh host 'cmd | - tail -f log | sed -n /ready/q'` fails three independent ways — the broker aborts - the long connection, the pattern can match a header printed before the work - starts, and `sed -n /x/q` exits the moment it matches, tearing down the job it was - watching. The only honest signal is a sentinel the job writes last, read over a - short-lived connection. -- **Never benchmark beside a live service, and never let a tool decide to stop one.** - The guard refuses before anything is built; unloading a production endpoint is a - human's call. A number taken as noise beside serving traffic is both disruptive and - wrong. -- **An instrument that segfaults is not an instrument.** Two of the three shipped - `.hip` probes called `hipGetDeviceProperties`, which crashes on this ROCm build - while `hipGetDeviceCount` works fine; device identity now comes from sysfs, and the - trap is recorded in the sources so it is not reintroduced. +Refs #53. `$HOME` had accumulated 123 loose scripts (archived to +`.AGENTTRASH/home-scripts-2026-10-20`; ~1.8 GiB of stray audio consolidated into `~/audiosamples`). +Two were durable instruments and now live in the repo with black-box tests and pages: +[tools/remote-job](tools/remote-job.md) — a readiness pattern in a log is not evidence of +completion, the pattern can match a header printed before the work starts, and `sed -n /x/q` +exits the moment it matches, tearing down the job it was watching — and +[tools/pcie-probe](tools/pcie-probe.md) — never benchmark beside a live service, never let a +tool decide to stop one, and an instrument that segfaults is not an instrument. ## 2026-10-20 — the secret gate was reading the wrong files, and one credential had already escaped -Found while deciding what else belongs in `dotfiles/`. (1) The gate whitelisted -extensions (`*.md *.yml *.json *.toml *.sh *.ts *.js`), so a `.env`, `.container`, -`.ini` or extension-less dotfile was never read — exactly the shapes the dotfiles -tier is made of. It now scans the whole staged diff for token shapes and, in -non-prose files, for credential assignments, and it fails closed when `git diff` -itself errors. That last clause earned its place: the rewrite's first attempt -passed `-I` to `git diff`, which is not valid there, got an empty file list, and -reported success on every commit — that is what a blind gate looks like, and it -looks identical to a clean tree. (2) A live credential had already escaped into -tracked content: a Caddy basicauth bcrypt hash, its plaintext password, and the -Pi-hole internal password, in an auto-learned memory file. Redacted — but both -credentials need **rotation**, because redaction does not un-leak history that -every machine has already pulled. (3) `home/dank/.omp/dotfiles/**` and -`.pre-pull-backup/**` were tracked fiction invisible to `deploy.sh`, and 23 -wallpaper JPEGs (80 MiB) rode along in every clone. Laws: a gate must fail closed, -since a silently-passing gate is worse than none — it buys the belief of safety; -auto-learned memory is written by a process that has read your terminal, so it is -a leak surface and gets gated like any config; `systemctl --user enable` -snapshots an absolute path, so enabling from a worktree means the seven-day reap +Found while deciding what else belongs in `dotfiles/`. The gate whitelisted extensions, so +`.env`, `.container`, `.ini` and extension-less dotfiles — the shapes the dotfiles tier is made +of — were never read. It now scans the whole staged diff for token shapes and, in non-prose +files, for credential assignments, and fails closed when `git diff` itself errors; the rewrite's +first attempt passed an invalid flag to `git diff`, got an empty file list, and reported success +on every commit — which is what a blind gate looks like, and it looks identical to a clean tree. +Bcrypt hashes and Basic-auth headers joined the shape list (issue #40) because the audit found +a live Caddy basicauth hash, its plaintext password, and the Pi-hole internal password in a +tracked auto-learned memory file. **Redaction does not un-leak history that every machine has +already pulled: both credentials needed rotation.** Auto-learned memory is written by a process +that has read your terminal, so it is a leak surface and gets gated like any config. Also +removed: tracked fiction invisible to `deploy.sh` (`home/dank/.omp/dotfiles/**`, +`.pre-pull-backup/**`) and 23 wallpaper JPEGs (80 MiB) riding in every clone. Mechanism: +`.githooks/pre-commit` and its tests; details in +[dotfiles-deploy](tools/dotfiles-deploy.md) and +[home-config-census](inventory/home-config-census.md). Law no other page owns: **`systemctl +--user enable` snapshots an absolute path** — enabling from a worktree means the seven-day reap silently kills the unit while `is-enabled` goes on saying `enabled` -(`blankpad-desk-autoswitch.service` was dangling that way). Details: -[dotfiles-deploy](tools/dotfiles-deploy.md), -[home-config-census](inventory/home-config-census.md). +(`blankpad-desk-autoswitch.service` was dangling that way). ## 2026-10-20 — engineering skills configured against this KB -`setup-matt-pocock-skills` ran, and its output moved out of the skill's default -`docs/agents/` into the knowledge base: [issue tracker](skills/issue-tracker.md) -(Gitea on Dankclaw is the tracker — no `gh`, no `tea`, token via a `600` curl -config), [triage labels](skills/triage-labels.md) (the five canonical roles, -created on the repo, which carried zero labels before), [domain -docs](skills/domain-docs.md) (single-context; glossary withheld until a term has -no other owner), plus an empty [decisions](decisions/index.md) with its format -rule. `AGENTS.md` gained an `## Agent skills` pointer block. Law: this repo has -one knowledge base, so per-tool configuration is a concept page in `omp-docs/` — -a second `docs/` tree means a second set of index rules, which is where -documentation starts disagreeing with itself. Known cost: re-running that setup -skill will not recognise `omp-docs/skills/*` and will offer to recreate -`docs/agents/`; the `AGENTS.md` pointer is the thing that stops it. +`setup-matt-pocock-skills` ran and its output moved out of the skill's default `docs/agents/` +into the knowledge base: [issue tracker](skills/issue-tracker.md), +[triage labels](skills/triage-labels.md), [domain docs](skills/domain-docs.md), and an empty +[decisions](decisions/index.md) with its format rule. `AGENTS.md` gained the `## Agent skills` +pointer block. Law: this repo has one knowledge base, so per-tool configuration is a concept +page in `omp-docs/` — a second `docs/` tree means a second set of index rules, which is where +documentation starts disagreeing with itself. Known cost: re-running that setup skill will not +recognise `omp-docs/skills/*` and will offer to recreate `docs/agents/`; the `AGENTS.md` pointer +is what stops it. ## 2026-10-19 — dankvoice auto-speak armed at startup -Sessions spoke only on explicit `/say`: the settings file carried no `auto` -key, and arming is session-scoped by design (`/say on` never persists). Set -`"auto": true` in `~/.omp/cache/dankvoice.json`. Same report, second half: -auto-speak listened only to `agent_end` — a settle that fires once per prompt -and is skipped outright when the harness schedules a continuation — so nothing -was narrated while an agent worked; fixed in dank/dankvoice#3, which narrates -`turn_end` as well. Law: silence at session start is a config state — read -`/say status` for `auto-speak on`, then watch `pgrep -a pw-play` for one player -per segment, before blaming the endpoint. Law: whether the harness delivers an -event to extensions is settled in minutes by a throwaway probe extension that -timestamps `turn_end`/`agent_end` — never inferred from audio timing, which the -humanizer lags by seconds. Details: [dankvoice](core/dankvoice.md). +Nothing spoke because the settings file carried no `auto` key (arming is session-scoped by +design) and narration rode only `agent_end`, a settle that a scheduled continuation skips; fixed +upstream in dank/dankvoice#3. Law: silence at session start is a config state — read `/say +status`, then watch `pgrep -a pw-play` for one player per segment — before blaming the endpoint. +Law: whether the harness delivers an event to extensions is settled in minutes by a throwaway +probe extension that timestamps `turn_end`/`agent_end`, never inferred from audio timing. +Details: [core/dankvoice.md](core/dankvoice.md). ## 2026-10-19 — LazyVim installed as an NVIM_APPNAME profile -`lazyvim` had never been installed here — shell history shows the command typed -twice and dropped; `~/.config/nvim` held only an orphaned `matugen.lua` palette -that nothing loaded. Now: LazyVim starter in `dotfiles/lazyvim/` symlinked to -`~/.config/lazyvim`, a `lazyvim` zsh function, and the palette applied through -`RRethy/base16-nvim` on `ColorScheme`. Law: the distro stays out of -`~/.config/nvim` so bare `nvim` keeps working as a vanilla quick-edit editor. -Details: [neovim](tools/neovim.md). +`lazyvim` had never been installed here: shell history shows the command typed twice and +dropped, and `~/.config/nvim` held only an orphaned `matugen.lua` palette nothing loaded. Now the +starter lives in `dotfiles/lazyvim/` symlinked to `~/.config/lazyvim`, a `lazyvim` zsh function +reaches it, and the wallpaper palette applies through `RRethy/base16-nvim` on `ColorScheme`. +Law: the distribution stays out of `~/.config/nvim` so bare `nvim` keeps working as a vanilla +quick-edit editor. Details: [tools/neovim.md](tools/neovim.md). ## 2026-10-19 — dankvoice cut over to the `dank/dankvoice` package (Refs #34) -Drop-in `agent/extensions/dankvoice.ts` and its 26-test suite retired; the -package carries the behaviour (41/41 black-box suite) and is installed with -`pi install git:`. Law: install and prove `/say status` speaks on a box -*before* pulling the cutover commit — the shared settings file keeps menu -state across both transports, so the swap is stateless once installed. +Drop-in `agent/extensions/dankvoice.ts` and its 26-test suite retired; the package carries the +behaviour (41/41 black-box suite) and installs with `pi install git:`. Law: install and prove +`/say status` speaks on a box *before* pulling the cutover commit — the shared settings file +keeps menu state across both transports, so the swap is stateless once installed. ## 2026-10-19 — dankvoice: silence and timeouts, root-caused (Refs #32) -The humanizer's raw `fetch` to frank-zealot omitted `chat_template_kwargs. -enable_thinking: false`, so the Qwen35 hybrid *thought* instead of answering: -6.9 s of `reasoning_content`, `content: ""` — timing out the rewrite twice -(12 s dead air) and, when it did answer inside the window, speaking silence. -Root-caused by replaying the exact wire body against the live endpoint. -Fix: pin the kwarg in the request body; treat empty-content-with-reasoning as -failure (mechanical voice), not "nothing worth saying"; the menu's test phrase -fires without blocking its handler. Lesson: the harness injects thinking-off -for managed roles, but raw HTTP callers must set it themselves — 26/26 green -with three red-captured regressions. +The humanizer's raw `fetch` omitted `chat_template_kwargs.enable_thinking: false`, so the Qwen35 +hybrid *thought* instead of answering: 6.9 s of `reasoning_content`, `content: ""` — dead air +twice, and when it answered inside the window it spoke silence. Root-caused by replaying the +exact wire body against the live endpoint. Fix: pin the kwarg, treat empty-content-with-reasoning +as failure, fire the menu's test phrase without blocking its handler. Lesson: the harness injects +thinking-off for managed roles; raw HTTP callers must set it themselves. +[core/dankvoice.md](core/dankvoice.md). -## 2026-10-19 — dankvoice: humanized narration and thinking aloud (Refs #27) +## 2026-10-19 — dankvoice: humanized narration, segmented speech, `/say` menu (Refs #27, #23, #21) -Spoken units are rewritten by the `@tiny` role (frank-zealot) under the -bundle's speech budgets before synthesis, with mechanical cleanup as fallback; -auto-speak narrates the last message's thinking blocks first; all toggles -persist. 7 new scenarios, 22 green; live smoke through real zealot + frank-tts. - -## 2026-10-19 — dankvoice: segmented sequential speech - -`agent_end` awaiting one whole-answer synthesis hit the harness's 30 s -extension-handler timeout — lesson: extensions must never await work that can -exceed 30 s. Ported the built-in vocalizer's utterance-splitting state machine -(boundary = `[.!?…]+` + whitespace, abbreviation/code guards, per-answer -segment budget); segments synthesize strictly sequentially. `DANKVOICE_MAX` is -now a segment budget. 17/17 in `tests/dankvoice.bats`. - -## 2026-10-18 — dankvoice: `/say` options menu with output-device routing (Refs #23) - -`/say menu` (pi-peon-style): auto-speak, voice, PipeWire output device -(`wpctl` → `pw-play --target`), player, char cap, test phrase. Precedence -env > `~/.omp/cache/dankvoice.json` > defaults — born from the incident where -PipeWire's remembered Bluetooth route played answers into nowhere. - -## 2026-10-18 — dankvoice: the cloned voices finally speak in the TUI (Refs #21) - -The harness gates `--voice` against the bundled Kokoro enum, so frank-tts's -cloned voices were unreachable in-TUI. Added `agent/extensions/dankvoice.ts` -(`/say …`) synthesizing over the OpenAI speech route, endpoint read from the -`api: openai-speech` row of `agent/models.yml` (no host or secret in code). +Spoken units are rewritten by the `@tiny` role under the bundle's speech budgets with mechanical +cleanup as fallback; the utterance-splitting state machine synthesizes segments strictly +sequentially because `agent_end` awaiting one whole-answer synthesis hit the harness's 30 s +extension-handler timeout — **extensions must never await work that can exceed 30 s**. `/say menu` +routes output device (`wpctl` → `pw-play --target`) with precedence env > settings file > +defaults, born from PipeWire's remembered Bluetooth route playing answers into nowhere. The +extension exists at all because the harness gates `--voice` against the bundled Kokoro enum, so +frank-tts's cloned voices were unreachable in-TUI. [core/dankvoice.md](core/dankvoice.md). ## 2026-10-18 — Noctalia bar layout clobbered by trunk-sync stash, recovered -GUI-built bar customizations existed only in the working tree; a trunk-sync -stash-then-drop destroyed them. Recovery: `git fsck --lost-found` surfaced the -dropped stash as a dangling commit; validated in a sandboxed `XDG_*_HOME` -before restore. Lesson: GUI-written config is only durable once committed — -the motivation for the live-sync heartbeat below. - -## 2026-10-18 — Fleet law: issue/PR attribution footers - -Every agent-opened issue/PR body ends with `cwd:` + `session:` footer, in both -`dank/command-center` and `dank/serverAppConfigs` AGENTS.md: machine-made -artifacts must trace to the box and session that produced them. +GUI-built bar customizations existed only in the working tree; a trunk-sync stash-then-drop +destroyed them. Recovery: `git fsck --lost-found` surfaced the dropped stash as a dangling +commit, validated in a sandboxed `XDG_*_HOME` before restore. Lesson: GUI-written config is +durable only once committed — the motivation for the heartbeat below. +[tools/dotfiles-deploy.md](tools/dotfiles-deploy.md). ## 2026-10-18 — live-sync heartbeat for machine-written paths (Refs #11) -A month of silent divergence in the live checkout proved the checkout-is- -deployment model needs a committer: `scripts/live-sync.sh` + systemd user -timer auto-commits allowlisted tiers (memories, managed-skills, live config, -dotfiles) with hooks armed, merges `origin/main`, pushes, reaps stale `wt/*`. -Non-allowlist dirt never staged; conflicts abort-and-alert. See -[tools/live-sync.md](tools/live-sync.md); tier law in AGENTS.md. +A month of silent divergence in the live checkout proved the checkout-is-deployment model needs +a committer: `scripts/live-sync.sh` + a systemd user timer auto-commits the allowlisted tiers +with hooks armed, merges `origin/main`, pushes, and reaps stale `wt/*`. Non-allowlist dirt is +never staged; conflicts abort and alert. [tools/live-sync.md](tools/live-sync.md); tier law in +`AGENTS.md`. Same day, two fleet laws into both repos' `AGENTS.md`: every agent-opened issue or +PR body ends with a `cwd:` + `session:` footer, so machine-made artifacts trace to the box that +spawned them; and `xdg-open ` the moment a PR opens, so Frank never hunts for it in +Gitea. -## 2026-10-18 — Fleet law: filed PRs open in Frank's browser immediately +## 2026-10-18 — SDLC switched to trunk-based development; extension route closed unmerged -`xdg-open ` the moment a PR opens — codified in both repos' git rules. - -## 2026-10-18 — Trunk law: extension route closed unmerged; direct repo alignment - -Operator closed PR #9 (cwd-scoped `before_agent_start` extension) in favor of -editing each repo's AGENTS.md. Durable findings: every context-file provider -(AGENTS.md walk-up, `.omp/` dirs, CLAUDE.md) resolves at or below the repo -root — ancestor-scope files can never load; user-level `APPEND_SYSTEM.md` -broadcasts to all sessions; serverAppConfigs never had git-flow. - -## 2026-10-18 — SDLC switched to trunk-based development - -`develop` retired (single-dev repo). Law in AGENTS.md § Trunk-based SDLC: -branch off freshest `origin/main`, PRs target `main`, `--no-ff` merges, tags -`vX.Y.Z` on main. Managed skill `gitflow-back-merge-plumbing` is historical. +`develop` retired (single-dev repo): branch off freshest `origin/main`, PRs target `main`, +`--no-ff` merges, tags `vX.Y.Z` on main — law in `AGENTS.md` § Trunk-based SDLC, managed skill +`gitflow-back-merge-plumbing` marked historical. The operator also closed PR #9 (cwd-scoped +`before_agent_start` extension) in favour of editing each repo's `AGENTS.md`. Durable finding: +every context-file provider (AGENTS.md walk-up, `.omp/` dirs, CLAUDE.md) resolves at or below +the repo root, so ancestor-scope files can never load; user-level `APPEND_SYSTEM.md` broadcasts +to all sessions. ## 2026-10-18 — Sibling-vault overlap census; system docs realigned to live ground truth -Census of `omp-docs/` vs `serverAppConfigs-docs` (subagents + live-ssh probes) -found four contradicting pairs; ours fixed, theirs flagged for their PR flow. -Recorded then: RTX 3090 moved Dankserver→Dankclaw 2026-09; dankserver = 2× -Radeon AI PRO R9700 (the "board swap" story was false); served models per -live units. Current state always: [/system/index.md](/system/index.md) -(27B-era zealot figures here are historical; live census 2026-10-19 below). +Census of `omp-docs/` vs `serverAppConfigs-docs` (subagents + live-ssh probes) found four +contradicting pairs; ours fixed, theirs flagged for their PR flow. Recorded then: the RTX 3090 +moved Dankserver→Dankclaw 2026-09; dankserver is 2× Radeon AI PRO R9700 (the "board swap" story +was false); served models per live units. Current state always: +[/system/index.md](/system/index.md). ## 2026-10-18 — Status line rebuilt on custom segments; full-source audit -New page [core/statusline.md](core/statusline.md) with an audit log — four -first-pass claims were false and corrected against the minified bundle + a -live screenshot matrix (context meter is `box`/`band` only; `composer.shape` -routes segment arrays; `mode` states = Plan/Prewalk/Goal/Vibe/Loop). Bar -rebuilt on `band` + custom segments; frank-archon display name slimmed in -models.yml. +[core/statusline.md](core/statusline.md) carries the audit log — four first-pass claims were +false and corrected against the minified bundle plus a live screenshot matrix (context meter is +`box`/`band` only; `composer.shape` routes segment arrays; `mode` states = +Plan/Prewalk/Goal/Vibe/Loop). Bar rebuilt on `band` + custom segments. ## 2026-10-09 — Blankpad DE redesign: desk profiles, keybinds, Handy wiring -Per-desk niri profiles keyed by EDID long name (connector names reshuffle -between docks; identity travels) + `scripts/desk.sh`; niri 26.04 has **no -hotplug event** (`OutputConfigChanged` is only an ACK per niri-ipc) — the -autoswitch daemon diffs output sets on `WorkspacesChanged`. Keybind cheat -sheet now generated (`scripts/gen-keybinds.sh`); noctalia docs corrected to -the v5 `noctalia msg` API. Handy: niri owns autostart; open breaker — -frank-secondary answers in `reasoning_content` with empty `content`, and -Handy cannot send `chat_template_kwargs.enable_thinking=false`. 27/27 green. +Per-desk niri profiles keyed by EDID long name (connector names reshuffle between docks; +identity travels) + `scripts/desk.sh`. niri 26.04 has **no hotplug event** (`OutputConfigChanged` +is only an ACK per niri-ipc), so the autoswitch daemon diffs output sets on `WorkspacesChanged`. +Keybind cheat sheet generated by `scripts/gen-keybinds.sh`; noctalia docs corrected to the v5 +`noctalia msg` API. Handy: niri owns autostart; open breaker — frank-secondary answers in +`reasoning_content` with empty `content`, and Handy cannot send +`chat_template_kwargs.enable_thinking=false`. [tools/niri/displays.md](tools/niri/displays.md), +[tools/handy.md](tools/handy.md). ## 2026-10-09 — Incident: agent sudo-loop tripped pam_faillock, locked out the operator -A subagent retried `sudo yay` (no TTY) for ~15 min; `pam_faillock` deny=3 -locked user `blank` — correct-password sudo failed, mimicking a password -change. Recovery: `faillock --user blank --reset`. Law: agents never run -`sudo`/`yay`/`pacman`/`pkexec`; root steps go to the operator as exact -commands. Skill `faillock-sudo-lockout-recovery`. +A subagent retried `sudo yay` (no TTY) for ~15 min; `pam_faillock` deny=3 locked user `blank`, +and correct-password sudo then failed, mimicking a password change. Recovery: +`faillock --user blank --reset`. Law: agents never run `sudo`/`yay`/`pacman`/`pkexec`; root steps +go to the operator as exact commands. Skill `faillock-sudo-lockout-recovery`. ## 2026-10-09 — Full secret audit of the command-center repo: clean -Five layers (live-value scan across 77 revs/765 blobs, whole object store -incl. dangling, shape triage, entropy ≥4.3, LFS payloads) — no secret in any -reachable or dangling object; quarantine list holds. Fixed `hf_token_write` -0644→0600. Open gap: the pre-commit gate lacks `hf_`/`npm_`/`pypi-`/`AIza`/ -Stripe/Telegram shapes and basic-auth URLs — widening it is an open decision. +Five layers (live-value scan across 77 revs/765 blobs, whole object store including dangling, +shape triage, entropy ≥4.3, LFS payloads) found no secret in any reachable or dangling object; +the quarantine list holds. Fixed `hf_token_write` 0644→0600. Open gap: the gate lacks +`hf_`/`npm_`/`pypi-`/`AIza`/Stripe/Telegram shapes and basic-auth URLs — widening it is an open +decision. ## 2026-10-09 — Gitea token moved out of every remote URL Remotes bare; credential in `~/.config/secrets/gitea` (0600) behind a global -`credential.helper store`. Trap (git 2.56.0, measured): `git -credential-store` rejects the `protocol=`/`host=` block shape the man page -shows — the store file must be one URL per line, port included. Verified with -`GIT_TERMINAL_PROMPT=0`. Details: [tools/gitea/client-access.md](tools/gitea/client-access.md). +`credential.helper store`. Trap (git 2.56.0, measured): `git credential-store` rejects the +`protocol=`/`host=` block shape the man page shows — the store file must be one URL per line, +port included. Verified with `GIT_TERMINAL_PROMPT=0`. +[tools/gitea/client-access.md](tools/gitea/client-access.md). ## 2026-10-09 — Working tree reconciled: callsign renames, compaction move, gitignore quarantine -200-entry dirty tree committed as seven atomic units. Harness model ids -renamed `frank-{medium,large,small}` → `frank-{archon,dragoon,zealot}` — -**services keep old names on the wire**; the engines accept any request model -id, so renames are harness-side only. Server-side compaction moved to -zealot's 3090 (threshold 70→85 %). `.gitignore` quarantine for worktree-keyed -memory buckets and runtime dirs. Skill renames are not losses: git renders -directory renames as mass delete+add; pre-cutover content intact at `12c5e88`. +200-entry dirty tree committed as seven atomic units. Harness model ids renamed +`frank-{medium,large,small}` → `frank-{archon,dragoon,zealot}` — **services keep old names on the +wire**; the engines accept any request model id, so renames are harness-side only. Server-side +compaction moved to zealot (threshold 70→85 %). `.gitignore` quarantine for worktree-keyed memory +buckets and runtime dirs. Skill renames are not losses: git renders directory renames as mass +delete+add; pre-cutover content intact at `12c5e88`. ## 2026-09-28 — Retired workstation purged from the knowledge base -Sold box: hostname, both LAN addresses, Wi-Fi SSID gone bundle-wide; the -knowledge distilled into [system/network.md](system/network.md) (multi-link -resolver law: a static resolver must be pinned on each *connection*) and -[tools/niri/displays.md](tools/niri/displays.md) (portrait transforms, the -three ways a multi-monitor rig breaks). Hostname stays only where it is a -lookup key (install.sh `--host`, `packages/.pkgs`). +Sold box: hostname, both LAN addresses, Wi-Fi SSID gone bundle-wide; the knowledge distilled +into [system/network.md](system/network.md) (multi-link resolver law: a static resolver must be +pinned on each *connection*) and [tools/niri/displays.md](tools/niri/displays.md) (portrait +transforms, the three ways a multi-monitor rig breaks). Hostname stays only where it is a lookup +key (install.sh `--host`, `packages/.pkgs`). ## 2026-09-01 — Retry backoff and thinking default (pi era) -Pi `settings.json`: 10 s base retry backoff (10/20/40 s), `defaultThinkingLevel: -medium` (no per-model thinking field exists in `models.json`). +Pi `settings.json`: 10 s base retry backoff (10/20/40 s), `defaultThinkingLevel: medium` (no +per-model thinking field exists in `models.json`). ## 2026-08-28 — KB rebuilt to OKF v0.1; live-verified fleet facts -`omp-docs/` copied from legacy `~/.pi/agent/docs`, rebuilt as git-tracked OKF -v0.1 (checkout *is* the deployment); unverifiable claims tagged `unverified`. -Conformance pass promoted concept-in-disguise indexes, stripped index -frontmatter, resolved all links; `scripts/kb-lint.sh` exits 0. Hardware facts -were corrected *then* (3090-on-dankserver since moved — see 2026-10-18). +`omp-docs/` copied from legacy `~/.pi/agent/docs`, rebuilt as git-tracked OKF v0.1 (checkout *is* +the deployment); unverifiable claims tagged `unverified`. Conformance pass promoted +concept-in-disguise indexes, stripped index frontmatter, resolved all links; `scripts/kb-lint.sh` +exits 0. Hardware facts were corrected *then* (3090-on-dankserver since moved — see 2026-10-18). Inventory census recorded under `inventory/` for the pruning session. ## 2026-08-23 — Homepage deployed to production; Caddy KB doc -Homepage live on Dankclaw via `scripts/deploy.sh` (status checker timer, -deployed.js, rendered basicauth hashes, health check). Hard-won Caddy lesson: -top-level directives are *sorted*, so `rewrite` runs before `basicauth` and -auth matchers saw rewritten paths — serve each page from its own `handle` -block to preserve order; a `handle` inline matcher is a single token; a -leading `@name ` parses as a matcher definition. Now documented on -[caddy/caddyfile.md](caddy/caddyfile.md). A compose mount change needs a -container *recreate*, not a reload. 116 tests green. +Homepage live on Dankclaw via `scripts/deploy.sh` (status checker timer, `deployed.js`, rendered +basicauth hashes, health check). Hard-won Caddy law, now on +[caddy/caddyfile.md](caddy/caddyfile.md): top-level directives are *sorted*, so `rewrite` runs +before `basicauth` and auth matchers saw rewritten paths — serve each page from its own `handle` +block; a `handle` inline matcher is a single token; a leading `@name ` parses as a +matcher definition. A compose mount change needs a container *recreate*, not a reload. ## 2026-08-23 — Homepage design settled (grilling complete) All 38 open questions answered; full record lives in serverAppConfigs -(`docs/Infrastructure/Homepage Design Grilling.md`). Key deltas: per-service -tcp/http probes, 5 s timeout, TUI-style panel grid, no client auto-refresh, -git-driven deploy, full TDD. Correction recorded: the Dankserver media stack -runs as bare processes, not containers — never probe it as a compose stack. +(`docs/Infrastructure/Homepage Design Grilling.md`). Key deltas: per-service tcp/http probes, 5 s +timeout, TUI-style panel grid, no client auto-refresh, git-driven deploy, full TDD. Correction +recorded: the Dankserver media stack runs as bare processes, not containers — never probe it as a +compose stack. ## 2026-08-22 — vllmScripts merged into serverAppConfigs -Full-history two-parent import; one repo, one checkout per host, one -`deploy.sh`. The plaintext sudo password from old docs was NOT carried over -(a bats leak-check guards it); it remains in both repos' history on the LAN -Gitea — rotation recommended. +Full-history two-parent import; one repo, one checkout per host, one `deploy.sh`. The plaintext +sudo password from old docs was NOT carried over (a bats leak-check guards it); it remains in +both repos' history on the LAN Gitea — rotation recommended. ## 2026-08-21 — Fleet ops: passwordless sudo, Hermes SSH keys -`/etc/sudoers.d/dank-nopasswd` on both servers (per-command drop-ins remain); -dedicated ed25519 key for the Hermes container, verified into all three -machines. +`/etc/sudoers.d/dank-nopasswd` on both servers (per-command drop-ins remain); dedicated ed25519 +key for the Hermes container, verified into all three machines. -## 2026-08-19 — pi-web-access: SearXNG endpoint re-pointed to dankclaw (pi era) +## 2026-08-19 — SearXNG + Gluetun bring-up; pi endpoint re-pointed -`searxngBaseUrl` moved off dead `.235:8081` to `.143:8081`; pi caches -`web-search.json` per process — new endpoint needs a new session. `yt-dlp` -installed (2026.07.04). - -## 2026-08-19 — SearXNG + Gluetun bring-up on dankclaw - -Durable field notes (now on concept pages — see +`searxngBaseUrl` moved off the dead `.235:8081` to `.143:8081`; pi caches `web-search.json` per +process, so a new endpoint needs a new session. Durable field notes now on concept pages — [agentic-web-research/vpn-routing.md](agentic-web-research/vpn-routing.md), [searxng-tuning.md](agentic-web-research/searxng-tuning.md), -[engineering/infra/containers.md](engineering/infra/containers.md)): -manual-WireGuard Proton needs `VPN_SERVICE_PROVIDER=custom` + the single-peer -`WIREGUARD_*` env names (not `protonvpn`, not `WIREGUARD_PEERS_*`); gluetun -ships no curl and `:8000` is the control server, not health; the limiter's -JSON cap (`API_MAX=4`/hour) is hardcoded Python — `pass_ip` is the only -bypass; rootless podman strips client IPs on published ports, so SearXNG must -sit behind Caddy for XFF; quadlet generator rejects `CapAdd=`/`Device=` and -generated units enable via `[Install] WantedBy=default.target`. ComfyUI on -gfx1151: build on AMD's ROCm base and filter the torch family out of -ComfyUI's requirements or you clobber the gfx1151 kernels. +[engineering/infra/containers.md](engineering/infra/containers.md): manual-WireGuard Proton needs +`VPN_SERVICE_PROVIDER=custom` + the single-peer `WIREGUARD_*` env names (not `protonvpn`, not +`WIREGUARD_PEERS_*`); gluetun ships no curl and `:8000` is the control server, not health; the +limiter's JSON cap is hardcoded Python and `pass_ip` is the only bypass; rootless podman strips +client IPs on published ports, so SearXNG must sit behind Caddy for XFF; the quadlet generator +rejects `CapAdd=`/`Device=` and generated units enable via `[Install] WantedBy=default.target`. +ComfyUI on gfx1151: build on AMD's ROCm base and filter the torch family out of ComfyUI's +requirements or you clobber the gfx1151 kernels. ## 2026-08-17 — Fleet redo + vLLM tool calling (pi era) -Fleet moved off the pipeline-parallel + NPU topology; Quark W8A8 quant chosen -over the Avesed INT8 for clean Qwen3 XML tool calls. +Fleet moved off the pipeline-parallel + NPU topology; Quark W8A8 quant chosen over the Avesed +INT8 for clean Qwen3 XML tool calls. ## 2026-08-16 — Gitea boot-persistence fix (dankclaw) -`unattended-upgrades` reboot exposed that the compose-era Gitea had no -systemd units (~1 h down). Stack rewritten as quadlet (names, `systemd-gitea` -network, linger + `WantedBy=default.target`, app.ini env-override gotcha, UID -100999 ownership) across `tools/gitea/*`; `podman generate systemd` marked -deprecated in [engineering/infra/containers.md](engineering/infra/containers.md). +`unattended-upgrades` reboot exposed that the compose-era Gitea had no systemd units (~1 h down). +Stack rewritten as quadlet (names, `systemd-gitea` network, linger + `WantedBy=default.target`, +app.ini env-override gotcha, UID 100999 ownership) across `tools/gitea/*`; `podman generate +systemd` marked deprecated in [engineering/infra/containers.md](engineering/infra/containers.md). ## 2026-08-13 — Pi subagent limits recorded (pi era) -Effective caps: globalConcurrencyLimit 2, parallel.maxTasks 8 / concurrency 4, -maxSubagentDepth 2, turnBudget 30; no per-model limit. See +Effective caps: `globalConcurrencyLimit` 2, `parallel.maxTasks` 8 / concurrency 4, +`maxSubagentDepth` 2, `turnBudget` 30; no per-model limit. [pi/packages/pi-subagents.md](pi/packages/pi-subagents.md). -## 2026-08-11 — Engineering research area; skills leaderboard verified +## 2026-08-11 — Engineering research area; agentic web research section -Created `engineering/` (SDLC, agent instruction files, stacks, infra, -plugins, skills ecosystem, Signal remote) via parallel research fleet. -Skills leaderboard replaced with the ranked list from the skills.sh API -(zero-based pagination, 200-skill pages). - -## 2026-08-11 — Agentic web research section; exhaustive bundle audit - -Created `agentic-web-research/` (8 pages) and rewrote them on 2025/2026 -evidence (SearXNG valkey deployment, real engine defaults, limiter budgets, -Gluetun pattern, open-model claim triage, MCP transports). Audit: 154+ -wikilinks resolved, dead external links fixed, `MemorySleepMode=deep` -verified valid (systemd 256+), ASD-STE100 style pass. `AGENTS.md` created as -the system behavior contract. +Created `engineering/` (SDLC, agent instruction files, stacks, infra, plugins, skills ecosystem, +Signal remote) and `agentic-web-research/` (8 pages), the latter rewritten on 2025/2026 evidence +(SearXNG valkey deployment, real engine defaults, limiter budgets, Gluetun pattern, open-model +claim triage, MCP transports). Skills leaderboard replaced with the ranked list from the skills.sh +API (zero-based pagination, 200-skill pages). Same pass: 154+ wikilinks resolved, dead external +links fixed, `MemorySleepMode=deep` verified valid (systemd 256+), ASD-STE100 style pass, +`AGENTS.md` created as the system behavior contract. ## 2026-08-06 — Link fixes; ComfyUI docs; major documentation overhaul -Fixed 15 broken wikilinks (missing frontmatter) + title mismatches → zero -broken. Added ComfyUI pages, per-host system docs, pi config tree, tools -entries; orphaned root duplicates moved to `.AGENTTRASH/`. +Fixed 15 broken wikilinks (missing frontmatter) + title mismatches → zero broken. Added ComfyUI +pages, per-host system docs, pi config tree, tools entries; orphaned root duplicates moved to +`.AGENTTRASH/`. ## 2026-07-18 — Maintenance tooling -Expanded `references/okf-maintenance.md` (cross-links, health metrics, -troubleshooting); created `core/log.md` and `.okf/config.yaml`. +Expanded `references/okf-maintenance.md` (cross-links, health metrics, troubleshooting); created +`core/log.md` (retired 2026-10-20, above) and `.okf/config.yaml`. ## 2026-01-15 — OKF conversion -Docs bundle converted to OKF v0.1; files migrated into `core/`, `tools/`, -`packages/`, `references/` with frontmatter on all files. +Docs bundle converted to OKF v0.1; files migrated into `core/`, `tools/`, `packages/`, +`references/` with frontmatter on all files. ## 2025-08-05 — Gitea documentation -Added [tools/gitea/index.md](tools/gitea/index.md) (topology, management, -data persistence) to the tools index. +Added [tools/gitea/index.md](tools/gitea/index.md) (topology, management, data persistence) to +the tools index — legacy tree, predating the OKF conversion above.