Compare commits

..
13 changed files with 321 additions and 45 deletions

No files matched your search

@@ -1,36 +1,35 @@
---
name: docs-dedupe-verify-campaign
description: "Run an exhaustive docs dedupe + fact-verification campaign in dank/command-center: scout cluster ledgers, evidence-overridden merges, live fleet probes, log compaction law, and landing via Gitea PR."
description: "Run an exhaustive docs dedupe + fact-verification campaign in dank/command-center: scout cluster ledgers, override scouts with evidence, gate every commit, and sweep scaffolding before PR."
---
Campaign procedure for deduping + fact-checking `omp-docs/` in dank/command-center (proven on Refs #29, 2026-10-19: 8 commits, -2,229 lines, 0 lint/coverage/bats failures).
# Exhaustive docs dedupe + verification campaign (dank/command-center)
## 1. Recon
- Fresh branch off `origin/main` in a new worktree (`git worktree add ~/.omp/wt/<name> -b docs/<issue>-<slug> origin/main`); the session worktree's `git checkout -b` is policy-blocked.
- Run `scripts/kb-lint.sh omp-docs` baseline; write a throwaway index-coverage checker (each dir's `index.md` must link every subdir/file) — mechanical, no scout needed.
- Open the Gitea issue first (multi-moving-parts law).
## Frame
- Read `omp-docs/references/okf-spec.md` + root `omp-docs/index.md` first; inventory with `find omp-docs -name '*.md' | wc -l`.
- Run `scripts/kb-lint.sh omp-docs` baseline; index-coverage gate is `scripts/kb-coverage.py omp-docs` (each dir's `index.md` must link every sibling file/subdir).
- Branch off fresh `origin/main` in a NEW worktree (`git -C ~/.omp worktree add ~/.omp/wt/<name> -b docs/<issue>-slug origin/main`) — workspace policy blocks `git checkout -b` in session worktrees.
- Open the Gitea issue first (multi-moving-parts law); branch name `docs/<issue#>-<slug>`.
## 2. Scout fan-out (overlap detection)
- One `task` batch, `agent: scout`, one task per doc cluster + one cross-cutting sweep (cap 2 concurrent; rest queue).
- Each scout writes a ledger to `local://dedupe/<cluster>.md`: inventory table (file, lines, purpose, verdict KEEP/MERGE/DELETE/UPDATE), overlaps, facts-to-keep ledger, inbound links, staleness, claims-needing-verification.
- Scouts are LEADS, not truth. Observed failures: fabricated ledger entries (claims about text that does not exist in any file), a DELETE verdict on a generated artifact that is under test (`tools/niri/keybindings.md` is generated by `scripts/gen-keybinds.sh` and tested by `tests/gen-keybinds.bats` — keep), and a KEEP based on misreading a changelog leftover. Re-derive every consequential verdict from the files/repo yourself.
## Scout fleet (overlap detection)
- One `task` call, `tasks[]` batch, agent `scout`, read-only; one cluster per task (skills trees, llama trees, pi vs packages, niri, sleep/power, core, gitea, gitea+cross-cutting sweep). Cap 2 concurrent; queue rest.
- Ledger format per cluster → `local://dedupe/<cluster>.md`: inventory table (file | lines | verdict KEEP|MERGE|DELETE|VENDOR|UPDATE), overlaps, facts-to-keep, inbound links, staleness.
- **Scouts lie.** Verified misses in the 2026-10-19 campaign: fabricated ledger entries ("peer-reviewed BrowseComp" claim that exists in no file), a DELETE verdict for a generated artifact that is checked in by design AND under test (`tools/niri/keybindings.md`, see `scripts/gen-keybinds.sh` + `tests/gen-keybinds.bats`), and a KEEP for a leftover the scout misread its own changelog about. NEVER apply a scout verdict without re-checking the file, the tests, and the changelog yourself.
## 3. Verify fleet facts live (never trust doc dates)
- Read-only BatchMode probes: `ssh -o BatchMode=yes dankclaw '...'` (podman ps/inspect, systemctl status, curl localhost, os-release, cpuinfo). `sudo -n` only has systemctl verbs — use `podman exec <ctr> grep/cat` for in-container files.
- Update docs with dated drift banners ("probed 2026-10-19") for policy divergence; NEVER silently re-apply a policy — flag as operator decision (e.g. SearXNG engine policy).
## Fact verification
- Fleet facts: read-only probes over `ssh -o BatchMode=yes dankclaw|dankserver` (podman ps/inspect/top, stat, /etc/os-release, curl localhost APIs, `podman exec <c> <tool> --version`). Live state beats docs even when docs claim recent verification — the 2026-10-18 "audit" still drifted (zealot 27B→9B, build 10754→10777, container census 14→32).
- External claims: web_search/web read yourself (no subagents per Frank's rule). Kill fabrications outright (invented distro `FreeToken`, nonexistent `ornith-llama-cpp-setup` tree, unattributable "44.5% peer-reviewed CUA") — delete/drop with an explicit note, never keep "plausible" numbers.
- Policy drift (live ≠ tuned docs): flag in the doc + PR body as an operator decision; NEVER silently re-apply policy.
## 4. Verify external claims via direct web research (no subagents)
- Version/protocol/benchmark claims: primary source only (upstream README, raw.githubusercontent, /api/v1/version, arxiv). If a number cannot be re-attributed to a published benchmark, drop it with an explicit note in the doc.
- Fabrication hunt: invented distros/projects/model names (found: "FreeToken" distro, ornith-llama-cpp-setup tree, Intel CPU on an AMD box) — delete the artifact, not just the line.
## Log compaction (log.md)
- Entry law: dated entry = what changed + durable lesson + concept-page pointer; narratives recoverable via `git log --follow`. Record the law at the top of the log. Tag historical-era entries.
## 5. Log compaction law
- Changelog entry = date + what changed + durable lesson + concept-page pointer, few lines. Full narratives live in git (`git log --follow`). Record the law at the top of `log.md`. Never re-expand an entry in place. Keep pi-era entries tagged "(pi era)".
## Gates + landing
- Gate every commit: `bash scripts/kb-lint.sh omp-docs && python3 scripts/kb-coverage.py omp-docs && bats tests/`. Pre-commit hook prints `error: command not found: caddy` on Caddyfile-touching commits — warning only, commit still lands.
- Before opening the PR: `git diff --stat origin/main..HEAD | grep -i 'tmp\|scratch\|\.bak'` — scaffolding merged under a `.tmp-` name in the 2026-10-19 campaign and needed a follow-up PR to promote.
- PR via API (skill `gitea-api-pr-operations` for token-extraction + merge payload); `xdg-open` the PR the moment it opens; comment the campaign ledger on the issue.
- After Frank's merge OK: propagate deployment `git -C ~/.omp fetch && git merge --ff-only origin/main`; reap campaign worktree (`git worktree remove --force`) + delete branch local/remote; close issue with landing comment.
## 6. Gates + landing
- Gate every commit: `bash scripts/kb-lint.sh omp-docs` + index-coverage + `bats tests/` (67 tests). Pre-commit hook prints `error: command not found: caddy` on Caddyfile-touching commits — warning only, commit still lands.
- Gitea API auth: `~/.config/secrets/gitea` is **URL-form** (`http://dank:<token>@host`), not a raw token. Extract: `T=$(sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p' ~/.config/secrets/gitea | head -1)`; raw `cat` as bearer token → 401.
- PR body ends with `---` + `cwd: <wd> · session: <id>` footer; then `xdg-open <PR-URL>` immediately; comment the campaign ledger summary on the issue. NEVER merge without Frank's explicit OK.
## Edit-tool field notes (recurring traps)
- `PUT N+=:` is invalid syntax — it inserts the literal text. One insert-op per edit call; recompute line numbers per call.
- File hash tag changes after every edit — re-`read` before each subsequent edit call to the same file; reusing a stale tag is rejected.
## Line-edit op syntax trap (bit three times)
- Valid ops: `PUT N.=M:` (body `+text` lines), `CUT N.=M` / `CUT N.=M @reg`, `PUT <N @reg`, `PUT >N:` / `PUT <N:`. There is NO `PUT N+=:` form — it lands in the file as a literal garbage line and shifts the insert to the wrong position. For gap-insert use a lone `PUT >N:` or `PUT <N:` per call; pre-compute line numbers against the file map (ops reference original lines within one call, but a failed op poisons positions).
- Recovery recipe when a literal op line lands: read exact bounds, `CUT N.=N` (junk), `CUT A.=B @block` (misplaced section), then `PUT <M @block` at the true anchor, re-read tail, add spacing blank if a `---` now abuts a list line.
@@ -0,0 +1,70 @@
---
name: docs-point-of-use-destruction
description: "Documentation exists for point of use, not archival. Use before any dedupe/compact/verify-docs job, and whenever a census or \"only copy\" argument is being used to KEEP documentation: apply the purpose test and destroy obsolete measurements. Supersedes the preservation conclusions of vault-one-fact-one-owner-dedupe §3/§12."
---
# Documentation exists for point of use
**Supersedes** `vault-one-fact-one-owner-dedupe` §3 ("port before cut") and §12
("expect docs flat") wherever those conclude a page must be *kept*. Sole custody of a
useless fact is still useless.
## The test, before you write and again before you keep
- Would the reader, doing a real task tomorrow, **act differently** because of this
line? No → delete it.
- Is it a measured rate (t/s, tok/s, ms, MiB-of-pool, acceptance %, a bench ladder) for
a model / runtime / layout that is **not running now and not being compared now**?
Destroy it. Not "file it in an Eras row", not "preserve it for history". AI moves
faster than the vault: the number is wrong before anyone reads it, and a wrong number
in a runbook gets believed.
- Keep the **law** the measurement taught; drop the measurement. Qualitative causal
stories survive; numbers do not.
- What survives: live config and its exact numbers, procedures, failure signatures,
security posture, interface contracts, rollback pointers.
- **Losing information is an acceptable, usually correct outcome.** Frank said so
explicitly on 2026-10-19 after rejecting a PR that had preserved 53 ledgers.
## Traps that lead agents to preserve junk
- **A census cannot decide what to keep.** `port-census.py`-style tools answer "where
does this fact live", never "does anyone need it". "14–63 sole-record lines per page"
was read as *irreplaceable* when the fact was *obsolete*. Ask usefulness, not custody.
- **A census that includes the file under test proves the page is held by itself.**
Exclude it, print the corpus size, assert absence; treat `0 unheld` on a long page as
a tool bug until proven otherwise.
- **"One fact, one owner" is a placement rule, not a preservation rule.** It says where
a surviving fact lives; it never says the fact must survive.
- **A change diary (`log.md`) is the largest archive in most vaults** (this one: 2,380
lines, ~8 % of the vault). Git holds the chronology; a causal law belongs on the page
that owns the thing it governs. Retire the diary.
## Execution that worked (2026-10-19, −13.7k lines)
1. Write the kill list as a reviewed artifact, one path per line, grouped by *why it is
dead* — that list becomes the PR's argument and the trash manifest.
2. Destroy whole pages first (`scripts/destroy-vault.py <list>`): move to
`.AGENTTRASH/<date>-destruction/`, then delete every reference from survivors, whole
line included when the dead link was its only content. Do not compare link targets
with `pathlib.Path.stem` — it strips real suffixes (`sd.cpp`).
3. Then strip numbers from what remains (`scripts/axe-measurements.py`): delete ledger
sections (noun-LEDS the heading, nothing instructional after it), blank any table cell
reporting a rate, fold Eras older than the current one into a single row pointing at
git. Never touch a shape number or a Laws/Wire/Rollback section.
4. Sweep indices (generator must exclude the trash dir, drop rows whose page cell became
empty), re-lint, re-check links.
5. Rewrite the governing policy in the same PR, or the next agent re-grows the vault:
project `AGENTS.md` *Documentation Policy* + the global agent settings.
## Report honestly, per area
docs / scripts / operator-doc separately, measured against the merge-base. The tools are
a cost that must be named, not hidden inside a headline "net" number — that is what got
the previous pass rejected.
## Verify the repair tools themselves
One repair pass matched "line ends with a comma" and deleted the trailing comma from 660
wrapped prose lines. Restore by matching lines that are byte-identical to `HEAD` except
for trailing punctuation (`restore-punctuation.py`), keep the destructive rule out of the
tool, and record the incident in its docstring.
@@ -0,0 +1,27 @@
---
name: gitea-merge-form-encoded
description: "Use when a Gitea API merge on Dankclaw (192.168.0.143:3000) returns HTTP 422 \"[Do]: Required\" — the merge endpoint needs form-encoded fields, not the JSON body that PR creation accepts."
---
Gitea on Dankclaw (192.168.0.143:3000, `dank/serverAppConfigs`) — merge quirk, measured 2026-10-19.
Creating a PR accepts a JSON body. **Merging does not**: `POST /api/v1/repos/{owner}/{repo}/pulls/{n}/merge` with a JSON body (`{"merge_style":"no-ff", "Do-Not-Reply-From":…, "Reply-To":…}`) or with those values as request headers returns `422 {"message":"[Do]: Required"}` — four variants of that all fail identically, so do not "fix" it by changing the style name or moving the headers around.
Working call (form-encoded):
```bash
tok=$(sed -n 's|http://[^:]*:\([^@]*\)@.*|\1|p' ~/.config/secrets/gitea | head -1) # git credential.helper store file
curl -sS -X POST -K <(printf 'header = "Authorization: token %s"\n' "$tok") \
--data-urlencode 'do=merge' \
--data-urlencode 'merge_style=no-ff' \
--data-urlencode 'Do-Not-Reply-From=chief@franksdank.cloud' \
--data-urlencode 'Reply-To=chief@franksdank.cloud' \
http://192.168.0.143:3000/api/v1/repos/dank/serverAppConfigs/pulls/<N>/merge
# 200 with an empty body == merged
```
- `do=merge` is the field the 422 was demanding (the web-UI form field name, not an API doc field).
- `merge_style` values are the dashed names: `no-ff`, `merge`, `rebase`, `squash`.
- Keep the token out of argv: `-K <(printf …)` feeds the `Authorization` header through stdin (a `-H "Authorization: token …"` lands in `/proc/<pid>/cmdline`, world-readable — the 2026-09-16 HF-token leak class).
- Verify by fast-forwarding a checkout (`git fetch && git merge --ff-only origin/main`) rather than re-reading the PR.
- Fleet law still applies: never merge without an explicit current OK from Frank, and open the PR URL in his browser (`xdg-open`) the moment it exists.
@@ -0,0 +1,29 @@
---
name: omp-extension-stale-module-audit
description: "Diagnose recurring omp extension handler timeouts/silence when the on-disk fix is already deployed: harness has no extension hot-reload, so running sessions freeze the module at session start (zombie modules)."
---
Use when a fix for an omp extension is merged/deployed yet the SAME errors persist ("handler timed out after 30000ms", silence, wrong behavior) in running sessions.
**Core fact:** the harness loads extension `.ts` modules at session start and never reloads them (no `/reload` for extensions; bundle `reload` hits are LSP/MCP/browser only). A session older than the fix keeps executing stale code regardless of the file on disk.
**Procedure:**
1. Census the errors from logs, not guesses — timeout lines carry the culprit pids:
`grep -h "handler timed out" ~/.omp/logs/omp.<today>*.log | python3 -c 'import sys,json; rows=[json.loads(l) for l in sys.stdin]; rows.sort(key=lambda r:r["timestamp"]); [print(r["timestamp"], r["event"], r["pid"]) for r in rows]'`
(`~/.omp/logs/omp.<date>.<pid>.log` — the pid in the filename is the session.)
2. Session start times vs deployed file versions:
`ps -eo pid,lstart,args | grep '[o]mp'`
`git log --format='%h %ad %s' --date=format:'%m-%d %H:%M' -- <extension path>`
`git reflog --format='%gd %ad %gs' --date=format:'%m-%d %H:%M' | grep merge` (live-sync pull times = when the file content changed).
Any session whose start time precedes the fix's arrival has the stale module.
3. Confirm the stale revision actually contains the bad pattern — read what that session loaded, not HEAD:
`git show <old-rev>:<path> | grep -n 'await speak\|async (event'`
(e.g. an `async agent_end` awaiting a >30 s chain is exactly what the harness's per-handler `Kzc` 30 s race logs; fire-and-forget `void` is fine — detached promises are NOT tracked by the dispatch window.)
4. Optional empirical proof when static analysis says "impossible": probe extensions through the real harness:
`omp -p --no-extensions -e /tmp/probe.ts "Reply OK"` with a probe that logs handler start/return and `void`s a 40 s detached promise + spawns a long child. Fast exit + no timeout log ⇒ detachment is untracked ⇒ blame stale modules, not the harness.
5. Remediation: restart the affected sessions (kill the listed pids or their terminals — the operator session itself may be one of the zombies; say so explicitly). There is no other fix.
**Gotchas:**
- Per-handler attribution in the log is accurate per dispatch; don't blame other extensions before checking the pid's start time.
- Endpoints healthy + silence persisting usually means TWO stacked bugs (e.g. stale awaiting module in old sessions AND a mute bug in current code) — diagnose each pid cohort separately.
- Extension chain code must use self-owned `AbortSignal.timeout(...)`, never a ctx-scoped signal: harness dispatch signals die when the dispatch window closes.
@@ -0,0 +1,29 @@
---
name: red-verify-without-stash
description: "Prove a regression test is red-capable against pre-fix code in a shared worktree where git stash/checkout are policy-blocked, via git-show blob swap with md5-verified restore."
---
# Red-verifying tests without `git stash`
## When
You fixed a file and added tests that should have failed before the fix, but workspace policy blocks `git stash` (and `git checkout -- file`) in shared worktrees (e.g. `~/.omp` shared-workspace policy). Needed to prove red-capture instead of asserting it.
## Recipe
One `bash` call — swap the fixed file for the HEAD (pre-fix) blob, run only the fix-relevant tests, restore, verify byte-identity:
```bash
cd "$WT"
md5sum path/to/fixed.file > /tmp/file-hash
cp path/to/fixed.file /tmp/fixed.file.bak
git show HEAD:path/to/fixed.file > path/to/fixed.file # pre-fix content; new tests now exercise old code
bats -f "test name A|test name B" tests/suite.bats # expect: not ok for every fix-relevant test
cp /tmp/fixed.file.bak path/to/fixed.file
md5sum -c /tmp/file-hash && echo RESTORED
```
## Notes
- `git show HEAD:path > path` truncates-and-refills the file in place; safe because you hold the md5 and a backup copy, and the restore check proves the fixed bytes are back.
- Run only the fix-relevant tests via the runner's name filter (`bats -f` regex); a full suite wastes time and old tests pass on both versions.
- Expect exactly the tests that pin the fix's behavior to fail pre-fix; tests unrelated to the change (pure coverage of old behavior) pass on both versions — don't count them as red evidence.
- `bats` pipelines mask exit codes without `pipefail`; judge by the `ok`/`not ok` lines, not the shell status.
- For non-test proof of endpoint behavior (live A/B of an HTTP request body), replay the exact body with `curl` against the live endpoint instead of speculating; keep both timings.
@@ -0,0 +1,43 @@
---
name: ryzenai-npu-oga-linux-spike
description: "Run a vendor-compiled AMD Ryzen AI (XDNA2) ONNX-GenAI model on a Linux Strix Halo host from public artifacts: staging, rootless NPU device-access wall, container recipe, and the NuTTS-air speech-token contract."
---
Run an AMD-precompiled XDNA2 model (OGA / onnxruntime-genai) on a Linux Strix Halo host (Dankclaw-class) without installing the vendor stack on the host.
## Where the bits actually live (both public, no AMD account)
- Runtime tarball: `https://download.amd.com/opendownload/RyzenAI/1.8.0/ryzen_ai-1.8.0.tgz` (~7.28 GiB). Verify with a ranged GET — first bytes `1f 8b 08`.
- NPU driver/XRT zip: `https://download.amd.com/opendownload/RyzenAI/Driver/RAI_1.8_Linux_NPU_XRT.zip` (90 MB).
- Dead ends (404, do not re-probe): `download.amd.com/driver/linux/RAI_1.8_Linux_NPU_XRT.zip`, `.../opendownload/RyzenAI/ryzen_ai-1.8.0.tgz`, `www.xilinx.com/bin/public/openDownload?filename=...` (301 → 404). The login-walled EULA *form* is not the gate; the `opendownload` tree is the real file host.
Tarball has 30 top-level entries: cp312/linux_x86_64 wheels (`onnxruntime_genai_ryzenai-0.14.0`, `onnxruntime_vitisai-1.27.0`, `vitis_mllib`, `flexml*`, `device_essentials_strx`, `model_generate`, `voe-1.8.0`), plus `deployment.tar.gz` (contains `lib/libonnxruntime-genai.so`, `lib/libonnxruntime_providers_ryzenai.so`, AIE compiler libs), `onnxruntime.tar.gz`, `quicktest.tar.gz` (VitisAI-EP install validator), `LLM.tar.gz` (text-only OGA examples: `run_model.py`, `tokenizer_factory.py`), `install_ryzen_ai.sh`. `voe-4.0-linux_x86_64.tar.gz` is only `vaip_config.json` — no TTS xclbins. **AMD ships no TTS runner and no TTS example.**
## Host rules
- **Never `dpkg -i` the vendor XRT.** `dpkg-deb -x <deb> <dir>` and bind-mount `<dir>/opt/xilinx/xrt` at the same path in the container. The plugin deb would install a DKMS `amdxdna` that replaces the distro in-tree driver; the in-tree driver on Ubuntu 26.04 is `amdxdna 0.7.0` and already loads `amdnpu/17f0_11/npu_7.sbin` at boot (`dmesg | grep amdxdna`).
- **Rootless containers cannot read `/dev/accel/accel0`** (`root:render 0660`): the host gid never enters the userns gid map, so `--device` yields `nobody:nogroup`. Verified failing 2026-10-19: `--group-add keep-groups`, `--userns=keep-id`, `--userns=keep-id:gid=990` (process gets gid 990, node still unreadable), `--group-add render`, `--group-add gid:990` (both error). Options: run rootful (`sudo podman`, separate storage → rebuild the image rootful, ~1 min), or add a world-writable udev rule for `accel0` (the fleet already does exactly this for `kfd`/`renderD*` in `99-comfyui-gpu-access.rules`, whose header explains why: systemd user-unit spawns carry no supplementary groups).
- **Rootless image build trap:** the vendor installer's `tar -xf` preserves recorded uids (95777 etc.) and aborts under `set -e` with `Cannot change ownership ... Invalid argument`, silently skipping the `deployment`/`onnxruntime` extraction. Patch it in the Containerfile: `sed -i 's/tar -xf/tar --no-same-owner -xf/g' install_ryzen_ai.sh`.
- Installer usage: `./install_ryzen_ai.sh -a yes -p /opt/rai -c /opt/rai` (python **3.12** required; base `python:3.12-slim`; needs `linux-libc-dev zip pciutils patch libgomp1`). It builds a venv from the local wheels, reinstalls `onnxruntime-vitisai` then `--no-deps onnxruntime-genai-ryzenai`, installs `requirements_extras.txt` (transformers 4.57.6), patches `bin/activate` (sets `RYZEN_AI_INSTALLATION_PATH=$VIRTUAL_ENV` + `LD_LIBRARY_PATH` into site-packages), and untars `LLM/deployment/onnxruntime/voe/quicktest` into the cpp path.
## HF artifact staging
- Gated (401 on API, 403 on git) for a plain account: `neuphonic/neucodec` (the **only** NeuCodec encoder), `neuphonic/neucodec-onnx-decoder`, `neuphonic/neucodec-onnx-decoder-int8`, `neuphonic/neutts-air`. `amd/neutts-air_rai_1.8.0_npu_4K` is public. Public mirrors of the codec exist (`eugenehp/neucodec` has the full 1.16 GB `pytorch_model.bin`) — treat a mirror of a gated release as a licensing decision for the operator, not a workaround to take silently.
- `github.com/neuphonic/neutts` is public and ships `samples/{dave,emily,greta,jo}.{pt,txt,wav}` = **pre-encoded reference codes + transcripts** → a spike needs zero gated bytes.
- Auth without leaking the token into `ps`: build a 600-perm credential file with a shell builtin and point git at it —
`umask 077; printf 'https://oauth2:%s@huggingface.co\n' "$(cat ~/.cache/huggingface/token)" > ~/.git-credentials-hf`
then `git -c credential.helper="store --file ~/.git-credentials-hf" clone ...`. Never `git clone https://oauth2:$TOKEN@...`.
## NuTTS-air on XDNA2 (the model-side contract)
- The package is an OGA model dir: `genai_config.json` (`type: qwen2`, 24 layers, hidden 896, 14 heads / 2 KV, ctx 32768, `provider_options: [{"RyzenAI": {"hybrid_opt_token_backend": "npu"}}]`), plus `dd_metastate_*` AIE artifacts and `model.onnx` + `model.onnx.data`.
- **Speech tokens live in the text vocab**: `<|speech_0|>` = id 151671 (10 001 codes), with `<|TEXT_PROMPT_START|>` 151666, `<|TEXT_PROMPT_END|>` 151667, `<|TEXT_REPLACE|>` 151665, `<|SPEECH_REPLACE|>` 151668, `<|SPEECH_GENERATION_START|>` 151669, `<|SPEECH_GENERATION_END|>` 151670. Generation is a plain `og.Generator` loop; the graph's own `eos_token_id` is `[151645, 151643]`, so pass 151670 as an extra eos or stop on it manually.
- **Real-time line = 50 speech tokens/s** (`sample_rate=24000`, `hop_length=480`). Judge any NPU/iGPU TTS number against 50.
- Build the prompt by *importing* the vendor function, never by copying the template:
`sys.path.insert(0, <neutts repo>); from neutts.neutts import NeuTTS`, then
`NeuTTS._apply_chat_template(SimpleNamespace(tokenizer=hf_tok, input_format="text"), ref_codes, ref_text, text, None)` — works on a duck-typed `self`, so no backbone/codec load. Template: `user\n<|TEXT_REPLACE|>\nassistant\n<|SPEECH_REPLACE|>`; splice `TEXT_PROMPT_START + encode(f"{ref_text} {text}") + TEXT_PROMPT_END` at TEXT_REPLACE, then truncate at SPEECH_REPLACE and append `[SPEECH_GENERATION_START] + encode("".join(f"<|speech_{i}|>"))`.
- Voice identity = reference codes + transcript (request-time conditioning). No trained artifact exists to preserve; packs holding only `reference.wav` require the gated encoder once, offline.
## Status caveat
Measured on 2026-10-19 up to and including: staged artifacts, image build, device-access matrix, prompt contract. The token-rate measurement (does the vendor userspace drive the in-tree 0.7.0 driver, and does it clear 50 tok/s) is **not** captured here — re-measure, never trust a remembered rate.
@@ -0,0 +1,44 @@
---
name: sigterm-kill-test-stub
description: "Pin \"code kills its child process\" behavior with a bash stub player: make the stub SIGTERM-reactive via sleep&wait, log KILLED via trap; diagnose deferred-trap failures."
---
Use when a test must prove production code sends SIGTERM to a spawned child (player, worker, server stub) and the test asserts the kill happened — or when such a test mysteriously fails with `KILLED` missing and wall time equal to the stub's sleep duration.
## The trap-during-sleep gotcha
A bash stub that sleeps in the **foreground** does NOT run its TERM trap when signalled: bash defers traps until the current foreground command completes. The stub dies (or logs) only after the sleep finishes — long after the test read the evidence file.
Symptoms:
- Assertion on a `KILLED` marker line fails although production code demonstrably calls `child.kill("SIGTERM")`.
- Test wall time equals the stub's full sleep (the kill "had no effect").
## Fix: reactive stub skeleton
```bash
#!/usr/bin/env bash
printf '%s\n' "$*" >> "$STUB_LOG" # call record
trap 'printf "KILLED\n" >> "$STUB_LOG"; exit 143' TERM # BEFORE the sleep
for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$COPY" ;; esac; done
sleep "${STUB_HOLD:-0.1}" & # background sleep…
wait $! # …wait is interruptible
printf 'DONE\n' >> "$STUB_LOG"
```
- `sleep N & wait $!` is the idiom: a signal makes builtin `wait` return immediately, the trap runs at once, `exit` inside the trap prevents fallthrough to `DONE`.
- Tune duration per test via the env knob (`STUB_HOLD=5`) so the child is provably still alive when the kill is triggered.
- Put the trap BEFORE any long command; a trap registered after the sleep never guards it.
## Harness wiring (black-box driver pattern)
- Scenario: start the long work → settle (>spawn latency, <hold) → trigger the killer (shutdown event, second invocation, `/stop`) → settle → read log.
- Expose the stub log lines on the driver's stdout (`LOG <line>` per line) so tests grep output, not files: black-box seam stays output-only.
- Assert `KILLED` present; for interrupt-semantics also count (`kills -eq 1`) and total call lines (`PLAYS 3` = call, KILLED, call).
## Orphan hygiene
Second child still sleeping when the driver exits: spawn with `stdio: "ignore"` so the orphan holds no capture pipe — it SIGPIPEs/quiets at teardown; do not lengthen tests waiting for it.
## Verify before trusting
Run the pair separately: kill-tests green while a byte-equality test (`cp` of the payload before sleep) stays green — the reactive rewrite must not drop the copy step (it sits between trap and sleep).
@@ -1,6 +1,6 @@
---
name: vault-one-fact-one-owner-dedupe
description: "\"Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census, doubled-page repair, trash-aware tools, delegated-write verification, index regeneration, and the merge-resolution order.\""
description: "\"Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census (corpus must exclude the file under test), doubled-page repair, trash-aware tools, delegated-write verification, honest per-area line accounting, index regeneration, and the merge-resolution order.\""
---
# One fact, one owner: dedupe the vault without losing a fact
@@ -37,12 +37,19 @@ file being rewritten. Any line with **no** surviving key is an orphan → port,
deliberately and say so. Use `scripts/port-census.py <new-dir> <git-HEAD:path|file>` (numbers
with ≥ 4 int digits, decimals, flags, paths, backticked spans, `--`, ALL-CAPS, or ≥ 2 hyphens).
Two calibration traps:
Three calibration traps:
- **Prose words are not facts.** A hyphen or trailing period inside a word (`long-standing`,
`deployed:`) was counted as a tracked token while real identifiers hid behind it; that noise
buried 300 real lines under 979 fake ones. Strip punctuation, require fact shape.
- **The corpus decides the question.** Corpus = the new file only → "what did this rewrite
drop?" Corpus = whole vault → "is the dropped fact orphaned repo-wide?" Ask one at a time.
- **The corpus MUST exclude the file under test, and you must assert it.** A census pointed at
the directory that *contains* the page proves the page is held by itself: ten era pages
reported `0 sole-record lines`, which reads as "pure duplication, trash it". With the file
excluded, the same pages held **14–63 sole-record lines each** — campaign ledgers that exist
nowhere else. A tool's own docstring promise ("excluding it") is not a check: print the
corpus size, assert the target is absent, and treat `0 unheld` on a long page as a tool bug
until proven otherwise. A mechanical census is a scout: its verdict is a lead, not a finding.
Also grep `AGENTS.md` separately — a fact it carries is not an orphan.
@@ -52,7 +59,7 @@ Signature: a page whose `wc -l` is ~2× its `HEAD` size, with a **second frontma
the body** whose closing `---` is glued onto the end of a body/timestamp line. Cause chain: a
frontmatter rewrite that re-reads the file it just wrote (so its own output becomes the body),
then a fence-repair pass keyed on `line == "---"` that cannot see a glued fence and inserts a
fence mid-page, so the next pass treats the tail as a new document.
fence mid-page, so the next page-tail becomes a new document.
Un-double rule that worked: find the first frontmatter's close (accepting a glued fence), then
the first `type:` line after it that has more keys (`title|description|tags|timestamp`) behind
@@ -82,9 +89,10 @@ grep -rnoE "\((page-a|page-b|...)\.md\)" --include="*.md" serverAppConfigs-docs
Both wikilinks and markdown path refs must be repointed (Research `See Also` lists and bench
pages are where path refs hide). Then **regenerate** index rows with a script, never by hand.
The generator bug to check for: presence tested as a **bare stem substring** — a page whose
name prefixes another's (`frank-medium` inside `frank-medium-rocmfpx`) then silently never
gets a row. Test the wikilink form `[[<stem>]]`.
Generator bugs to check for: presence tested as a **bare stem substring** — a page whose name
prefixes another's (`frank-medium` inside `frank-medium-rocmfpx`) then silently never gets a
row (test the wikilink form `[[<stem>]]`); and **no dead-row sweep**, so deleting a page leaves
an index row that fails the link check — the generator must drop rows for absent pages too.
## 7. Enforce the law with a seam
@@ -96,6 +104,12 @@ re-created era page then cannot pass tests. For the length hint, prefer **derive
owned by the page that measured it) plus one documented per-page opt-out; never let a size
ceiling force a ledger to be split across two owners.
Splitting an over-long page to satisfy a ceiling is usually line-NEGATIVE: one measured split
turned a 700-line page into 488 + 306 across five files (+23 for the folder net of stub
deletions), because frontmatter blocks, pointer sections and connective prose are charged per
file while the facts move for free. Roll the split back, carry `okf-exempt: length` with the
reason, and keep the stub pages deleted.
## 8. Edit-tool hazard (costs the most time)
Body rows beginning with `-` or `+` are op markers. The documented `+-` escape **writes a
@@ -104,6 +118,10 @@ line untouched and PUT only the indented continuation lines, or convert the note
bullet whose body lines start with two/four spaces. If a wrapped `[[Two Word Page]]` shows up
as an unresolved link, the target was hard-wrapped across lines — unwrap it.
A `PUT` that spans a multi-line statement can silently drop neighbouring assignments
(auto-repaired boundary echo); after editing a constants block, `grep -n "^[A-Z_]* = "` and
run a syntax probe before executing.
## 9. Delegated writes: verify bytes, never narrative
Measured in one campaign, all four failure modes live:
@@ -146,7 +164,24 @@ matter how concrete they read; and re-read the source bullet before trusting a t
- Open the PR through the API with a 600-perm curl config (managed skill
`gitea-api-pr-operations`), end the body with the `cwd:` / `session:` footer, then
`xdg-open <PR-URL>` the instant it exists. Never merge without Frank's current explicit OK.
Push with an explicit refspec (`HEAD:refs/heads/<pr-branch>`) — a worktree's own branch name
is not the PR branch, and `git push origin HEAD` opens a stray branch.
- Docs-only change ⇒ **no deploy**. Fast-forward the deploy checkout only
(`git -C ~/Projects/serverAppConfig merge --ff-only origin/main`).
- Say what was NOT done, in the PR body: soft lint tallies left (missing `## Citations`),
conflicts left standing, repo config that was never installed on a host.
## 12. Report line counts per area, never one net
"Compaction" measured against the merge-base **per area**: docs / `scripts/` / `AGENTS.md`.
A single headline number hides that tooling and prose move in opposite directions — one
campaign reported "−1,849 vault lines" while the vault was flat and the growth was the two
enforcement scripts that made the law mechanical. Expect a mature one-owner vault to land
**docs flat**: the reductions available are deletions of *restatement* (duplicated pages,
transient deploy chatter in `log.md`, `AGENTS.md` bullets whose numbers an endpoint page owns)
and they are roughly cancelled by the cost of the citations the verification pass adds. When
asked to shrink further, show the menu — drop the tools, drop the citations, or accept content
loss — instead of inventing cuts. `log.md` compaction rule that survived census: drop a line
only when it matches a transient shape (`/health`, `deployed`, `restart`, `systemctl start`)
**and** every fact token in it is held by an owner page; a token-held line can still be the
only record of *when* an incident happened.
@@ -1,4 +1,4 @@
{
"v": 1,
"lastConsolidatedAt": 1792398289746
"lastConsolidatedAt": 1792432172049
}
@@ -1,8 +1,8 @@
{
"v": 1,
"lastConsolidatedAt": 1792426412619,
"lastConsolidatedAt": 1792437458615,
"lastResult": {
"at": 1792401456699,
"at": 1792433135862,
"sessions": 1,
"deltas": 1,
"model": "frank-zealot"
@@ -1,10 +1,10 @@
{
"v": 1,
"lastConsolidatedAt": 1792426569902,
"lastConsolidatedAt": 1792437542473,
"lastResult": {
"at": 1792402864677,
"at": 1792433353188,
"sessions": 1,
"deltas": 8,
"deltas": 1,
"model": "frank-zealot"
}
}
@@ -1,10 +1,10 @@
{
"v": 1,
"lastConsolidatedAt": 1792426408958,
"lastConsolidatedAt": 1792434572397,
"lastResult": {
"at": 1792401394555,
"at": 1792432178813,
"sessions": 1,
"deltas": 1,
"deltas": 2,
"model": "frank-zealot"
}
}
@@ -275,13 +275,13 @@ transition = [ "fade" ]
interval_seconds = 1200
[wallpaper.default]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_ymp267.jpg"
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
[wallpaper.last]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_ymp267.jpg"
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
[wallpaper.monitors.DP-1]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_y8ewzk.jpg"
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
[wallpaper.monitors.DP-2]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
@@ -290,7 +290,7 @@ transition = [ "fade" ]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_v9e2w3.jpg"
[wallpaper.monitors.eDP-1]
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_ymp267.jpg"
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_oxex69.jpg"
[weather]
unit = "imperial"