Compare commits

..
Author SHA1 Message Date
dank ab94c91bcf Merge pull request 'One work log per bundle: the rule, the gate that enforces it, and the second log it came out of' (#57) from docs/single-work-log-rule into main 2026-10-20 22:23:21 -07:00
dank 5cf7e6bd8e Merge pull request 'APPEND_SYSTEM: report a PR as its link plus its title — and commit the doctrine that has been live but uncommitted since Oct 19' (#56) from docs/pr-reference-rule into main 2026-10-20 22:23:17 -07:00
dank 1cc4b76479 Merge pull request 'gen-keybinds: a C locale lost every section header — match the dash as a literal' (#55) from fix/gen-keybinds-locale-safe-sections into main 2026-10-20 22:21:29 -07:00
Frank 59920f849d docs(agent): re-add the work-log rule, restricted to one file
Rule 1 lost its work-log sentence in the point-of-use rewrite. Restoring it plainly would
re-create the old failure, where one agent kept logs in fifty-odd files and none of them
was the place to look. So the restored rule names the file: a single page in the
knowledge base — omp-docs/log.md here, whichever change-log page a repo's index names
elsewhere — read before starting, written in the same commit as the change it describes,
under that page's own entry law. Explicitly banned: per-directory logs, per-project or
per-run logs, NOTES.md/PROGRESS.md/STATUS.md/worklog/, dated diaries. If a KB has no log
page, the fix is one at its root plus an index entry, never a log somewhere else.

The bundle's own log records the change and the two-log incident it came out of.
2026-10-20 21:49:16 -07:00
Frank bfd6a6340e docs(okf): the work log is one file at the bundle root, and an entry is a lesson not a manifest
The spec listed `log.md` as a reserved name and the maintenance page said every bundle
"has a `log.md`" — neither said there is only one, so a subdirectory could invent its own
and look compliant. Which is how the sprawl Frank banned started: with fifty logs the
entry is never where the next reader looks, and every copy re-dates the same event.

The maintenance page also taught the wrong format: "one line per change with file path",
exampled as `/docs/ghostty.md — Updated keybind table`, i.e. a manifest duplicating git,
with absolute paths that kb-lint cannot resolve because OKF links are relative. Replaced
with the law this bundle already runs: what changed plus the durable lesson, a few lines,
relative links, laws and incidents rather than bookkeeping.
2026-10-20 21:49:16 -07:00
Frank 4849f46eb2 feat(kb-gates): one work log per bundle, and the quarantine is not bundle content
The bundle had two work logs. `core/log.md` was created "for tracking core directory
modifications" and holds two entries of pure migration bookkeeping (file moved,
directory created, log created) — every one recoverable from `git log` — plus a date
for the OKF conversion (2026-07-15) that matches neither the bundle log (2026-01-15)
nor git history, where the OKF rebuild lands 2026-08-28. `core/index.md` was still
linking it, which is exactly how a stale page stays in circulation. kb-lint now reports
any `log.md` that is not the bundle root's; the page moved to omp-docs/core/.AGENTTRASH/
per the trash convention, and the core index points one level up.

Both gates also walked `.AGENTTRASH/`, so trashing a page turned them red: kb-lint
demanded frontmatter from a quarantined page and kb-coverage demanded an index for the
trash directory. A gate that only goes green once the quarantined copy is deleted is a
gate that deletes history, so both skip any path containing `.AGENTTRASH` — pinned by a
test in each suite, and kb-coverage finally gets its own test file.
2026-10-20 21:49:15 -07:00
Frank dd91ef1d59 fix(gen-keybinds): match the section dash as a literal so headers survive LC_ALL=C
The section rule was `/^[[:space:]]*\/\/[[:space:]]*─+ (.+) ─+…$/`, a quantifier on a
multibyte character. Under LC_ALL=C — which is what cron, systemd user units and agent
shells get, regardless of LANG — gawk matches byte-wise, so `─+` means "the last byte of
U+2500, repeated" and never matches. Measured on the live keybinds.kdl: the old script
emitted 0 section headers instead of 12, all 120 binding rows intact, exit status 0. A
silent one-third-of-the-page loss, invisible to any caller that only checks the status.

The dash is now matched and stripped as a literal (`gsub(/─/, "")`), which is byte-safe
in every locale, and a comment must be dash-decorated on both sides to open a section, so
plain comments are still ignored. A test pins the C-locale case explicitly, because the
box's own terminal has always been UTF-8 and could never have caught this.

The committed artifact omp-docs/tools/niri/keybindings.md is unaffected (12 headers,
120 rows — it was generated from a UTF-8 shell); output is now identical either way.
2026-10-20 21:27:16 -07:00
Frank 289f74062a docs(agent): a pull request is reported as its link plus its title, never a bare number
"#54" made Frank go hunting: numbers are per-repository across the forges this
fleet uses, and a number carries no hint of which work is being reported. Every
mention of a PR now pairs its web URL with its title — gitea.franksdank.cloud for
the Dankclaw Gitea, the equivalent for any other forge.

Refs the AGENTS.md convention that an open PR gets opened in Frank's browser: the
rule covers prose, where the browser tab does not help.
2026-10-20 21:22:35 -07:00
Frank d7bd05398d docs(agent): commit the point-of-use documentation doctrine that has been live for a day
The live deployment has been running this rewrite of rule 1 since 2026-10-19, but
the file was never committed: agent/APPEND_SYSTEM.md is prose tier, outside the
live-sync allowlist, so the heartbeat surfaces the dirt instead of committing it —
which is correct behaviour, and it left the fleet's system prompt with no history
and no way for another machine to pull it. Committed verbatim as it runs, no
editorial changes: the purpose test, the dead-weight-number law, and the
deletion-is-an-update clause.

A config that only exists on one disk is not a deployment; it is a pending loss.
2026-10-20 21:20:04 -07:00
dank 004962ffce Merge pull request 'Rescue the detached job runner and the PCIe probe instruments from $HOME sediment' (#54) from feat/53-fleet-probes into main 2026-10-20 19:39:08 -07:00
Frank 56fc899763 Ship the PCIe probe instruments with a guard that refuses to contend with a service
The RDNA4 offload campaign left three HIP probes and a driver sitting loose in
$HOME, where the next agent cannot find them and a stray `rm -rf` ends them. They
answer a recurring question on the R9700 box — is decode limited by the link or by
the engine's read path — so they move into scripts/probes/ with a driver, tests,
and a page.

Two things changed in the code, not just the address:

- The driver measures only after a guard that runs BEFORE anything is built:
  gpu_busy_percent over PCIE_PROBE_MAX_BUSY, or any process holding /dev/kfd, is a
  refusal (exit 3) rather than a measurement. A benchmark taken beside a live
  endpoint is both disruptive and worthless, and silently unloading a production
  service is not a tool's decision — --force is the human saying otherwise. The
  refusal-before-build ordering is pinned by a test, because the sysfs and kfd
  paths are overridable and a refusal is otherwise untestable off the card box.
- hip_smoke.hip and pcie_probe.hip called hipGetDeviceProperties, which SEGFAULTS
  on this ROCm build even though hipGetDeviceCount works; only pcie_bw.hip avoided
  it, and that is the one that produced the campaign's numbers. Device identity now
  comes from the sysfs topology block the driver prints, so all three probes are
  runnable on the build we ship. Verified here: refusal on a real busy APU (exit 3),
  missing toolchain (exit 4), missing sources (exit 2). Compile-and-run still needs
  the box with hipcc.

Refs #53
2026-10-20 19:24:58 -07:00
Frank 35ceae6d7b Add remote-job.sh: a detached job whose result outlives the session
`ssh host 'cmd | tail -f log | sed -n /ready/q'` reported success on jobs that
never finished, three structural ways: the broker aborts the long connection, a
readiness pattern can match a log header printed before the work starts, and
`sed -n /x/q` exits the instant it matches — tearing down the very command it
was watching. The replacement runs the job detached on the host and writes
NAME.out, NAME.rc, then NAME.done LAST, so status is a cheap read of files over
a short-lived connection. That survives the connection, the restart, and the job
that restarts the endpoint the agent itself is running on.

Black-box tests pin the sentinel ordering (rc before done), that a finished-but-
failed job is DONE rc=N with the report still exiting 0, and that a name with no
state is never reported DONE.

Refs #53
2026-10-20 19:24:15 -07:00
dank 655a39ffb6 Merge pull request 'pkg-inventory.sh: detect a machine that has outgrown packages/*.pkgs, and declare blankpad's 24 missing ones' (#52) from feat/51-pkg-inventory into main 2026-10-20 18:20:07 -07:00
Frank b4fc881164 Declare the 24 hand-installed blankpad packages the manifest was missing
The point of the new inventory tool is this commit: packages/blankpad.pkgs declared one package
while the machine had 24 more that no rebuild would reproduce. Declaring them changes nothing on
the running box (install.sh installs only what is missing — its delta is now empty) and makes a
fresh install faithful to this one.

nirimod-git is left undeclared on purpose: it is AUR-only and aur.pkgs is fleet-wide, so declaring
it there would make the headless servers build a niri module.

Refs #51
2026-10-20 18:08:23 -07:00
Frank 6df45d709a Add pkg-inventory.sh: report what a machine has outgrown its manifests
install.sh installs desired − installed from packages/*.pkgs, which guarantees a fresh box reaches
the declared state — and nothing anywhere checked that the declared state still describes the
machines. Measured against the Calamares install day, blankpad carries 41 hand-installed packages
and packages/blankpad.pkgs declared one line.

pkg-inventory.sh walks the other direction: installed − declared, with the pacman log as the
baseline so installer furniture is never reported as a hand install. It splits additions by
whether a sync db can serve them (Chaotic-AUR counts, so only true AUR routes to aur.pkgs),
excludes orphans from --emit, reports flatpak apps without pretending install.sh installs them,
and exits 3 rather than guessing when the sync databases are unreadable.

16 black-box tests over fixtures and a stubbed pacman. Two of them exist because the first
implementation was wrong in ways a small fixture cannot show: sort|head under pipefail dies of
SIGPIPE on a real 1000-line install-day log (awk-exit closes the pipe exactly as head does), and
tr without a trailing echo glues the next report label onto the last folded name.

Docs: omp-docs/tools/packages.md now owns the package tier — install.sh was never documented.

Refs #51
2026-10-20 18:07:42 -07:00
dank 5ab0a49ea4 Merge pull request 'Correct the lock-watcher claims: session restart was the trigger; suspend was covered by noctalia' (#49) from fix/47-attribution-and-inhibitor-correction into main 2026-10-20 17:55:08 -07:00
Frank 4666d0dce0 Correct the lock-watcher law: the trigger is the session restart, and suspend was covered
Two statements in the law added by #48 need measurement rather than inference, and one of them was
wrong.

**Attributed.** `niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026` (`ps -o lstart=`);
swayidle's last state change was `16:22:23`. The compositor session restarted and the watcher died
with it — eight seconds of agreement, which is what `PartOf=graphical-session.target` is for. The
fix that #48 justified by reasoning is now justified by the event itself. The user journal holds no
entries for that window, so the evidence is process ages, not logs.

**Corrected.** #48 said closing the lid suspended blankpad unlocked, because `before-sleep` lived in
the dead unit. `systemd-inhibit --list` says otherwise: `noctalia … sleep "Lock before sleep" delay`
— the shell holds its own sleep inhibitor and locks on suspend independently of swayidle, so the
suspend path held all along. What was dead for eight days is the **idle** lock and `after-resume`.
The method error is worth keeping: `pgrep` for lockers came back empty and I read that as "no lock
path", when a locker can be a hook inside a running shell. The inhibitor table is the instrument.

Added with them, because it answers a question people will ask: while an `omp` session runs, swayidle
logs `Not enabling timeouts: idle inhibitor found` and arms no idle timeout (`omp … idle "omp agent
session" block`). A box with an agent working does not lock on inactivity, by harness design.

Docs only; the unit file from #48 is unchanged and the live watcher is `active` with `NRestarts=1`
after the SIGTERM proof. `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0 issues, `bats tests/`
71/71. Refs #47.
2026-10-20 17:52:59 -07:00
dank d013795922 Merge pull request 'blankpad has had no screen lock for 8 days; the lid suspends it unlocked' (#48) from fix/47-lock-watcher-resilience into main 2026-10-20 17:25:11 -07:00
Frank 2b7b0bc3cd Make the idle-lock watcher survive a clean exit, and outlive a session restart
`swayidle-lock.service` was `enabled`, in `default.target.wants/`, and **dead for eight days**:

    Active: inactive (dead) since Mon 2026-10-12 16:22:23 PDT; 1 week 1 day ago
    Duration: 2d 21h 57min   Main PID: 3181 (code=exited, status=0/SUCCESS)
    Result=success  NRestarts=0

`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. So the
watcher that owns both the 5-minute idle lock and the `before-sleep` lock stopped existing, and
nothing on the box noticed. The consequence is not cosmetic: with `HandleLidSwitch=suspend` and
`HandleLidSwitchExternalPower=""` (which falls back to it), **closing the lid suspended the
machine unlocked**, because the hook that locks on sleep lived inside the unit that had died. No
alternative was running: `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, and
neither `autostart.kdl` launches a locker.

Three settings, each with a job:

- `Restart=always` — a supervisor must not assume it can only die badly.
- `StartLimitIntervalSec=0` — with `always`, a fast loop must never wedge the unit into `failed`;
  a lock that keeps trying beats a lock that stopped trying.
- `PartOf=graphical-session.target` — it is wanted by `default.target`, so a session restart
  brought the target back without it; now the watcher is restarted with the session.

What triggered the 16:22 exit is **unattributed**: the user journal has no entries in that window,
so all that can be said is that swayidle exited with status 0 — precisely what the old policy was
blind to. The fix does not depend on knowing the trigger, which is the point.

Verification is behavioural, per the law recorded with it: start the unit, `kill -TERM` the main
PID, and watch `NRestarts` increment with a new `MainPID` — the October event, survived. Not
performed here: starting it arms a 5-minute idle lock on a machine that has gone a week without
one, and that is Frank's call. `systemd-analyze --user verify` clean, `bats tests/` 71/71,
`kb-lint.sh omp-docs` 0, `kb-coverage.py` 0.

Refs #47
2026-10-20 17:24:32 -07:00
dank 3809e1ed22 Merge pull request 'deploy.sh declared this repo's own heartbeat unit foreign, on every box' (#46) from fix/44-unit-specifier-resolution into main 2026-10-20 17:22:52 -07:00
Frank e278e4b863 Expand systemd specifiers before calling a unit foreign
The first real `deploy.sh` run on blankpad printed:

    SKIP   live-sync.service (ExecStart '%h/.omp/scripts/live-sync.sh' absent on this host)

while `/home/blank/.omp/scripts/live-sync.sh` sits right there. `%h` is expanded by systemd,
never by the shell, so the literal string was handed to `command -v`, failed, and the unit was
declared to belong to another machine — through the same code path whose whole purpose is to
keep a *foreign* unit inert, and silently. The victim was this repo's own heartbeat, and the
verdict was the same on every box that ever deployed it, because `%h` is exactly where the
checkout lives.

Nothing broke tonight only by luck of install state: `live-sync.service` is `linked`, driven
by its timer, so the heartbeat kept running while the deploy lied about it. A unit whose only
install route was this loop would simply never have been enabled anywhere.

`%h`, `%u` and `%U` are now expanded before resolution; an unrecognised specifier is left
alone so it fails loudly rather than being guessed at. Pinned by a test that writes a
`%h/.local/bin/helper` unit into the scratch home — red before (`SKIP`), green after
(`already linked; not started`), which also keeps the #45 rule intact: resolving a unit is not
licence to start it. Refs #44.

Verification: `bats tests/` 71/71, `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0
issues, `bash -n` clean.
2026-10-20 17:19:49 -07:00
dank c05f5ad451 Merge pull request 'deploy.sh is unsafe beside a live session: it starts a screen lock and imports another box settings' (#45) from fix/44-deploy-live-safe into main 2026-10-20 17:16:47 -07:00
30 changed files with 1760 additions and 58 deletions
+18 -3
View File
@@ -54,13 +54,27 @@ Suggest these tools or languages as possible solutions to problems if they are a
### 1. Maintain your projects source of truth.
The knowledge base at `docs/` is the source of truth for your project. Your training is out of date. Default to the documentation and the code before you assume anything. Maintain a work log of actions taken and their result. Refer to the work log to remember the progress of your tasks.
Documentation exists for ONE reason: **point of use**. A page earns its bytes only if it helps you perform a task at the moment you perform it — the shape to deploy, the flag that must be passed, the failure mode to recognise, the rollback to run. It is NOT a museum, NOT a lab notebook, and NOT a place to keep track of numbers.
**THE PURPOSE TEST — apply it before you write, and again before you keep:**
- Would an agent or Frank, doing a real task tomorrow, read this line and act differently because of it? If no: **delete it.**
- Is this a measured number (t/s, tokens, ms, MiB, %, bench ladder, acceptance rate) for a model, runtime, or hardware configuration that is NOT currently deployed and NOT currently being compared? Then it is **dead weight — destroy it**, do not archive it, do not "preserve it in an Eras row", do not cite it. AI advances make such a figure wrong within weeks, and a wrong number in a runbook is worse than no number: it gets believed.
- "It is the only copy of that fact" is NOT a reason to keep it. Sole custody of a useless fact is still useless. Losing information is an acceptable — often correct — outcome; do not hesitate to lose it.
- What survives the test: live configuration and its exact numbers, the procedure that must be followed, the law/causal story that prevents a repeat failure, security posture, and the interface contract a consumer depends on. Qualitative laws outlive the measurement that taught them — keep the law, drop the numbers.
- Never let a tool's "this is duplicated / this is the only record" census decide what to keep. That is a question of USEFULNESS, which only the operator answers.
1. Read the relevant `index.md` before you touch a topic.
2. Follow `omp-docs/references/okf-spec.md` for every doc you create or edit.
3. Read the target file completely before you edit it.
4. Always update the documentation when you've completed a task.
5. Compact the work log when necessary.
4. Always update the documentation when you've completed a task — and when updating means deleting a page or a paragraph, delete it.
5. Compact the work log when necessary; a work log holds laws and incidents, never bookkeeping.
#### THE WORK LOG IS ONE FILE.
The work log is a single page in the knowledge base: `omp-docs/log.md` in this repo, or whichever change-log page the repo's docs index names. Write the entry when the change lands — in the same commit as the change — and read that page to recover where things stand before starting. It follows its own entry law: a dated entry is what changed plus the durable lesson, a few lines.
- NEVER create a second log: not per directory, not per project, feature, branch or run, not `NOTES.md` / `PROGRESS.md` / `STATUS.md` / `worklog/`, not a dated diary. Logging in fifty places is not logging — the entry is never where the next reader looks, and each copy re-dates the same event on its own authority. In `dank/command-center` the KB linter reports any `log.md` that is not the bundle root's.
- A log holds laws and incidents, never bookkeeping: no per-file inventories, no run-by-run numbers, no transcripts. A number in a log still has to pass the purpose test above.
- If a knowledge base has no log page, add one at its root and list it in the index. That is the fix — never a log somewhere else.
#### USE PROGRESSIVE DISCLOSURE.
Always ensure that all produced documentation is laid out in files following WIKI conventions: start with a top level README.md index file that provides a brief, concise introduction to the item being documented, and then SPLIT OFF individual aspects, features, or arguments into their own interlinked pages so that future agents can read the documentation in a piecewise fashion, only taking in the details they need. Where relevant, crosslink any created documentation with other relevant documentation. This structure may also be nested, if necessary.
@@ -90,6 +104,7 @@ Never assume the user reports correct information. He is human, and humans make
- Before starting new work, `git pull` from the remote, and start a new branch in a separate worktree using `master` or `main` as the base branch.
- Commit atomically: after every change that forms a complete functional unit, commit. Do so early and often.
- When the work is complete, ask the user if they would like to open a pull request.
- **Report a pull request as link AND title, every time.** A bare `#54` is not a reference: numbers are per-repository, so a number alone does not say which work, in which repo, is being reported. Give the PR's web URL on that forge — on the Dankclaw Gitea that is `https://gitea.franksdank.cloud/<owner>/<repo>/pulls/N` — together with the PR's title. Applies everywhere a PR is mentioned: chat replies, status summaries, issue and commit bodies, alerts. Another forge (GitHub, GitLab, …): the equivalent URL its web UI serves, same pairing.
### 5. Test Driven Development
@@ -2,6 +2,13 @@
Description=swayidle idle lock (locks via noctalia-shell IPC, suspends on lid)
Documentation=https://github.com/swaywm/swayidle
After=graphical-session.target
# Restart together with the session, not merely at boot: this unit is wanted by
# default.target, so a compositor restart used to leave it orphaned while the target came
# back without it. Refs #47.
PartOf=graphical-session.target
# With Restart=always below, a fast failure loop must never wedge the watcher into `failed`:
# a lock that keeps trying beats a lock that stopped trying. Refs #47.
StartLimitIntervalSec=0
[Service]
# Locks via the noctalia v5 shell; `noctalia msg` resolves the running instance
@@ -11,7 +18,10 @@ ExecStart=/usr/bin/swayidle -w \
timeout 300 '/usr/bin/noctalia msg session lock' \
before-sleep '/usr/bin/noctalia msg session lock' \
after-resume '/usr/bin/noctalia msg session lock'
Restart=on-failure
# `swayidle -w` exits 0 on SIGTERM, and that is how this watcher died on 2026-10-12: a clean
# exit that `Restart=on-failure` cannot see, leaving the box unlocked for a week. A supervisor
# restarts always. Refs #47.
Restart=always
RestartSec=5s
[Install]
+1 -3
View File
@@ -16,6 +16,4 @@ These files define the structural conventions that govern the agent system itsel
* [OMP Status Line Customization](statusline.md) — How presets, composer shapes, and segment arrays actually interact; the fleet's tuned bar. (moved from /pi/, 2026-10-19)
* [OMP Subagent Limits & Fan-out Policy](subagent-limits.md) — Live caps: concurrency 2, no nested spawns (the `maxRecursionDepth: 0` trap), per-endpoint in-flight ceilings, isolation policy. (moved from /pi/, 2026-10-19)
## Change Tracking
* [Bundle Change Log](log.md) — Chronological update history.
**Change log:** the bundle keeps exactly one, one level up — [Bundle Change Log](../log.md). A directory here never has its own.
-21
View File
@@ -1,21 +0,0 @@
---
type: changelog
title: Core Bundle Change Log
description: Change log for the core documentation directory — tracks migrations and structural changes.
tags:
- core
- changelog
- okf
timestamp: 2026-07-18
---
# Core Bundle Change Log
## 2026-07-18
- **Migration**: Converted `index.md` (root) to `core/index-doc.md` — moved root bundle index into core domain
- **Migration**: Converted `agents-md-format.md` (root) to `core/agents-md-format.md` — agents-md format belongs in core
- **Creation**: Established `core/` directory with `index.md` as the domain entry point
- **Creation**: This change log for tracking core directory modifications
## 2026-07-15
- **Initialization**: OKF v0.1 conversion of the entire docs bundle — frontmatter applied, structure established, first `log.md` entries created
+44
View File
@@ -16,6 +16,50 @@ lesson, a few lines. Full incident narratives are recoverable from git
(`git log --follow omp-docs/log.md`); knowledge that outlives an entry lives
on its concept page, linked here. Never re-expand an entry in place.
This page is the bundle's **only** work log: one file, at the root. Never start a
second one in a subdirectory or under another name — `scripts/kb-lint.sh` reports any
`log.md` that is not this one.
## 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.
## 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.
## 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
+37 -15
View File
@@ -21,32 +21,46 @@ Knowledge bundles rot. It is the nature of knowledge to attract cobwebs, much li
## Change Tracking with `log.md`
Every OKF bundle has a `log.md` file. It is not optional. It is the bundle's memory.
Every OKF bundle has a `log.md`, and it is the bundle's **only** work log: one file,
at the bundle root. A subdirectory never gets its own — not `core/log.md`, not
`notes.md`, not a per-project, per-branch or per-run diary. Sprawl is not caution:
with fifty logs the entry is never where the next agent looks, and each copy re-dates
the same event on its own authority. `scripts/kb-lint.sh` reports any `log.md` that is
not the root's.
**Format:** Date-grouped entries, newest first. One line per change with timestamp, file path, and what changed.
**Format:** one dated section per day, newest first; a few lines per change — what
happened, and the durable lesson. It is not a file manifest.
```markdown
# Bundle Change Log
## 2026-01-15
- `/docs/ghostty.md` — Updated keybind table after v1.2 release
- `/docs/pi-permission-system/` — Added `policy.md` and `audit.md`
- `/docs/index.md` — Synced with directory contents
## 2026-01-15 — the gate was reading the wrong files
A gate that gets an empty file list reports success. Law: a check MUST fail closed;
a silently-passing gate is worse than none, because it buys the belief of safety.
Details: [secret gate](../tools/index.md).
```
**Rules:**
- **Newest entries first.** Putting old entries at the top inverts the timeline and makes it impossible to see what just happened.
- **One line per change.** A single line is sufficient. The git history is there if someone wants the gory details.
- **Group by date.** Batch all changes from the same day together.
- **Always date-stamp the section.** Use `## YYYY-MM-DD`.
- **Mention `index.md` whenever you change a bundle.** If you added or removed a file, update the index and note it in the log.
- **Newest entries first.** Old entries at the top invert the timeline.
- **What changed + the durable lesson, a few lines.** Full narratives are recoverable
from git (`git log --follow log.md`); never re-expand one in place.
- **Laws and incidents, never bookkeeping.** No per-file inventories, no run-by-run
numbers, no transcripts. Measured numbers still have to pass the purpose test.
- **Link, do not duplicate.** Knowledge that outlives the entry belongs on its concept
page; the entry points there.
- **One dated section per day** (`## YYYY-MM-DD`), and mention `index.md` whenever a
file is added or removed.
### Common Mistakes
- ❌ Writing `"Updated ghostty.md"` — Updated it with what? Be specific.
- ❌ Forgetting to write to `log.md` — This is the equivalent of doing surgery without keeping a record.
- ❌ Using relative paths — Use absolute paths (`/docs/ghostty.md`).
- ❌ Leaving stale entries from manual edits — The log is the single source of truth.
- ❌ `Updated ghostty.md` — updated it with what, and what did it teach?
- ❌ Forgetting the entry — the change lands undocumented and the next agent rediscovers
the failure the hard way.
- ❌ Starting a second log in a subdirectory — the sprawl that makes logging useless.
- ❌ Invented absolute paths in links — links resolve relative to the page, and the
linter checks them.
- ❌ Re-expanding an old entry in place — the entry is a pointer; git is the transcript.
---
@@ -84,6 +98,14 @@ OKF is built on the principle that a file is one concept.
- ❌ Don't create a file called `misc.md` — `misc` is a graveyard, not a category.
- ❌ Don't use `# Other` or `# Additional Notes` sections — these are taping things to the wall.
## Quarantine, Not Deletion
A page is never deleted outright: it moves to `.AGENTTRASH/` beside where it lived and
waits for a pruning session with Frank present. The quarantine is not part of the
bundle — `scripts/kb-lint.sh` and `scripts/kb-coverage.py` skip any path containing
`.AGENTTRASH`. That exemption is deliberate: a gate that goes green only once the
quarantined copy is gone is a gate that deletes history.
---
## Auto-Generation Strategies
+8 -2
View File
@@ -42,7 +42,7 @@ An OKF knowledge bundle is a directory tree. Every concept is one file. The file
```
my-bundle/
├── index.md # Optional directory listing
├── log.md # Optional update history
├── log.md # The bundle's ONE update history — root only
├── concept-one.md # A concept at the root level
└── subdirectory/
├── index.md # Optional subdirectory listing
@@ -55,7 +55,13 @@ my-bundle/
| Filename | Purpose |
|----------|---------|
| `index.md` | Directory listing for progressive disclosure |
| `log.md` | Chronological update history |
| `log.md` | Chronological update history — **exactly one, at the bundle root** |
**One work log per bundle.** `log.md` is reserved, and it is reserved for a single
file: the bundle root's. A subdirectory never keeps its own change log — a bundle
that logs in fifty places has no log, because the entry is never where the next
reader looks and every copy re-dates the same event. `scripts/kb-lint.sh` reports
any `log.md` that is not the root's.
---
+69
View File
@@ -158,6 +158,27 @@ fresh box — where everything reports `disabled` — still gets its units up. B
are pinned in `tests/deploy.bats` over a stub `systemctl`, because the real one on this box
is exactly what must not be invoked from a test. Refs #44.
## Law: expand systemd specifiers before calling a unit foreign
`ExecStart=%h/.omp/scripts/live-sync.sh` is valid systemd and meaningless to a shell. The
resolution check handed that literal string to `command -v`, which fails everywhere, so the
deployment concluded the unit belonged to another machine and skipped it — silently, through
the same code path that is supposed to keep a *foreign* unit inert. The unit it kept skipping
was this repo's own heartbeat, and `%h` is precisely where this checkout lives, so the false
verdict was the same on every box that deployed it. Measured on blankpad: the first real
`deploy.sh` run printed `SKIP live-sync.service (ExecStart '%h/.omp/scripts/live-sync.sh'
absent on this host)` while `/home/blank/.omp/scripts/live-sync.sh` sits right there.
Expand the specifiers a user unit can carry — `%h`, `%u`, `%U` — before resolving, and let an
unknown specifier fail rather than guess at it. The probe needs no privileges and writes
nothing:
```bash
bin=$(sed -n 's/^ExecStart=\([^ ]*\).*/\1/p' \
~/.omp/dotfiles/systemd-user/.config/systemd/user/live-sync.service | head -1)
command -v "${bin//%h/$HOME}" # resolves → the unit is this box's; the SKIP was the lie
```
## Law: the dconf dump holds preferences, not per-box state
`dconf load /` writes every key in `dotfiles/dconf/dump.txt`, so the dump carries whatever
@@ -184,3 +205,51 @@ it in the other. Re-dump on the box whose settings are canonical (`dconf dump /
dotfiles/dconf/dump.txt`), then delete the state sections before committing — the census's
*Never track: app state* class applies inside a tracked file, not just to whole directories.
Refs #44.
## Law: a watcher must restart on a clean exit too
`swayidle-lock.service` was `enabled`, present in `default.target.wants/`, and **dead for eight
days**: `Result=success`, `NRestarts=0`, gone since 2026-10-12 16:22 after 2d 21h of uptime.
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. The trigger is
attributable to eight seconds: **`niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026`**
(`ps -o lstart=`) while swayidle's last state change was `16:22:23` — the compositor session
restarted, the watcher went with it, and because it was wanted by `default.target` rather than tied
to the session target, nothing brought it back. The user journal keeps no entries for that window,
so the attribution rests on process ages rather than logs, which is its own lesson: diagnose a
lapse within the retention you actually have.
**The severity claim in this page's first draft was wrong, and is corrected here.** It said closing
the lid suspended the machine unlocked, because the `before-sleep` hook lived in the dead unit. But
`systemd-inhibit --list` shows `noctalia … sleep "Lock before sleep" delay` — the shell holds its
own sleep inhibitor and locks on suspend independently of swayidle, so the suspend path was covered
the whole time. What was actually dead for eight days is the **idle** lock: five minutes of
inactivity locked nothing, and `after-resume` never fired. `pgrep -c -u blankpad
'swayidle|swaylock|hyprlock|gtklock'` = 0 remains true; it simply did not mean *no lock path at
all*, because a locker can be a hook inside a running shell rather than a name on the process list.
Read the inhibitor table, not only `pgrep`.
Three settings, each with a job: `Restart=always`, because a supervisor must not assume it can
only die badly; `StartLimitIntervalSec=0`, because a fast loop must never wedge it into `failed` —
a lock that keeps trying beats one that stopped trying; and `PartOf=graphical-session.target`,
because the unit is wanted by `default.target`, so the session coming back did not bring *it*
back.
Verify by behaviour rather than by reading the file — the point is surviving the event that killed
it in October:
```bash
systemctl --user start swayidle-lock.service
kill -TERM "$(systemctl --user show swayidle-lock.service -p MainPID --value)"
sleep 8 && systemctl --user show swayidle-lock.service -p NRestarts -p MainPID -p ActiveState
```
An idle inhibitor also disarms the timeout outright, which surprises people: while an `omp` agent
session is running, swayidle logs `Not enabling timeouts: idle inhibitor found` and arms no idle
timeout at all — `systemd-inhibit --list` shows `omp … idle "omp agent session" block`. A box with
an agent working therefore does not lock on inactivity, by harness design, while `before-sleep` and
`after-resume` keep working. Anyone asking "why did the screen never lock" should read the inhibitor
table before suspecting the unit.
The general shape: `deploy.sh` only ever *enables*. Nothing on any box re-checks that a unit the
system depends on is still running, so a security posture can lapse silently and stay lapsed for a
week. A liveness check for this tier belongs in the heartbeat, not in a human's memory. Refs #47.
+3
View File
@@ -19,6 +19,9 @@ Configuration guides and reference material for installed software. Each tool ha
* [neovim](neovim.md) — Two editors in one binary: vanilla `nvim` for quick edits and the `lazyvim` NVIM_APPNAME profile, with the base16 wallpaper-palette hook and fresh-box install.
* [niri](niri/index.md) — Scrollable-tiling Wayland compositor — configuration, keybindings, and architecture for the workstation.
* [noctalia-shell](noctalia-shell.md) — Native desktop environment for Niri — launcher, control center, settings, lock screen, session menu, hotkey overlay, media controls, and system management.
* [packages](packages.md) — `packages/*.pkgs` desired state and the two tools that keep a machine and its manifest agreeing: `install.sh` installs the delta, `pkg-inventory.sh` reports what the machine has outgrown.
* [pcie-probe](pcie-probe.md) — HIP probes that separate PCIe link bandwidth from the engine's read path at the expert-slab size, and the refuse-by-default guard against benchmarking beside a live service.
* [remote-job](remote-job.md) — Detached job runner for `ssh` work: completion sentinels on the host replace the `tail -f | sed -n /ready/q` pattern that reported success on jobs that never finished.
* [tmux](tmux.md) — Terminal multiplexer configuration, extended-keys setup, keyboard input best practices, and reload/restart procedures.
## Adding a New Tool
+106
View File
@@ -0,0 +1,106 @@
---
type: reference
title: package desired state
description: packages/*.pkgs plus the two tools that keep a machine and its manifest agreeing — install.sh installs desired-minus-installed, pkg-inventory.sh reports installed-minus-declared.
tags:
- packages
- pacman
- provisioning
- fleet
- blankpad
verified: 2026-10-20
timestamp: 2026-10-20
---
# Package desired state
Two tools, one invariant: **the manifests must describe the machine.** A rebuild is only faithful
if both directions are checked.
|tool|direction|writes|
|---|---|---|
|`scripts/install.sh`|desired → installed|installs `desired − installed` with `--apply`; never removes|
|`scripts/pkg-inventory.sh`|installed → declared|nothing — read-only report|
## Manifests
`packages/common.pkgs` (every host) + `packages/<host>.pkgs` (this host) + `packages/aur.pkgs`
(AUR-only, installed through `yay`). The parser is `sed 's/#.*//'` then the first field, so `#`
comments trail freely and one token per line is the whole grammar. An unknown `--host` gets the
common manifest only: a wrong-host run is a no-op, not a hazard.
## Rebuild a workstation
1. Install the OS (CachyOS, niri/noctalia profile). The installer's own package set is *not* in the
manifests and must not be — see the baseline law below.
2. Clone this repo to `~/.omp`, then `scripts/install.sh --apply` — packages first, because the
enablement step needs `sshd` and `firewalld` present ([dotfiles-deploy](dotfiles-deploy.md)).
3. `scripts/deploy.sh sync` for the config tier ([dotfiles-deploy](dotfiles-deploy.md)).
4. `scripts/pkg-inventory.sh --emit` on a *known-good* machine and fold the output into
`packages/<host>.pkgs` / `packages/aur.pkgs` before step 2 — that is how a hand install stops
being invisible to the next rebuild.
## pkg-inventory report
```
undeclared additions (declare these): N ← exit 1 when N > 0
repo (sync dbs): <names> → belongs in packages/<host>.pkgs
aur (needs a helper): <names> → belongs in packages/aur.pkgs
installer-provided, undeclared: N + names → a reinstall supplies these; do not declare
declared, not installed: <names> → install.sh installs these
orphans ignored: <names> → pacman -Qtdq
flatpak: <app ids> → reported only, install.sh installs no flatpaks
```
Exit codes: `0` clean · `1` drift · `2` usage · `3` classification impossible. Overrides
`PKGINV_REPO` / `PKGINV_HOST` / `PKGINV_LOG` / `PKGINV_BASELINE` exist for tests.
## Law: the installer's own day is the baseline
Whether a package is *yours* is decided by the pacman log, not by opinion: the baseline date is the
oldest `[ALPM] installed` entry — Calamares' pacstrap — and everything installed **on or before** it
is installer furniture. Without that cut, a base install reports several hundred "additions" and the
list nobody can act on is worse than no list.
- **Read the rotated logs too** (`pacman.log.N`, `.N.gz`). On a machine older than the rotation
window the install day exists only there; skip them and the whole base system is reported as
hand-installed.
- Calamares keeps installing the desktop for minutes after the base transaction, which is why the
date cut is inclusive.
## Law: refuse rather than guess the AUR split
`aur.pkgs` vs `<host>.pkgs` is decided by whether any sync database can serve the name — Chaotic-AUR
counts as a sync database, so only true AUR belongs in `aur.pkgs`. If the sync databases are
unavailable the tool exits `3` instead of assuming: guessing sends every package to `yay`, which is
how a manifest becomes a pile of source builds.
Classify with `pacman -Sl` (world-readable). **`pacman -Sqq` requires root** — a tool that only
reads the machine must not need sudo to answer a question about it.
**Known gap:** `aur.pkgs` is read for *every* host, so a desktop-only AUR package (a niri module, a
GUI client) cannot be declared there without asking the headless servers to build it. Such a name is
still reported as an AUR addition; leave it undeclared until `install.sh` grows a per-host AUR
manifest.
## Law: orphans never reach a manifest
`pacman -Qtdq` names are packages nothing depends on; emitting them would teach a fresh machine to
install the same dead weight. They are printed as `orphans ignored` and excluded from `--emit`.
## Two traps that produced this tool
- **`set -o pipefail` plus `head -1`**: `head` closes the pipe, `sort` dies of `SIGPIPE`, the
substitution returns `141`, and `set -e` aborts the script. It survives a small test fixture and
fails on a real 1000-line install-day log — take a minimum in one pass (`awk 'END'`-style), never
sort-then-first. The same applies to `awk 'NR==1{exit}'`, which closes the pipe exactly as `head`
does.
- **`tr '\n' ' '` swallows the final newline** and `fold` faithfully preserves that absence, so the
next report label lands on the same line as the last name. `tr '\n' ' '; echo` instead.
## Related
- [tools/index](index.md) · [dotfiles-deploy](dotfiles-deploy.md) (the config tier that follows) ·
[live-sync](live-sync.md) (the heartbeat that commits machine state)
- Tests: `tests/pkg-inventory.bats` (fixtures + stubbed `pacman`, no real package database touched).
- Refs #51.
+92
View File
@@ -0,0 +1,92 @@
---
type: reference
title: pcie-probe — link or read path
description: HIP instruments that separate PCIe link bandwidth from the engine's read path at the real expert-slab size, plus the refuse-by-default guard that stops a benchmark from running beside a live service.
tags:
- gpu
- rocm
- rdna4
- benchmarking
- dankserver
verified: 2026-10-20
timestamp: 2026-10-20
---
# pcie-probe
`scripts/pcie-probe.sh` answers one question, which comes up every time MoE
expert offload is discussed on the R9700 box
([dankserver](../system/dankserver.md)): **is the stall the PCIe link, or is it
the engine's read path?** The two look identical from the outside — decode is
slow while weights stream in — and they have opposite fixes.
## Instruments
Sources in `scripts/probes/`, built with the host `hipcc`:
| Probe | Question it answers |
|-------|---------------------|
| `hip_smoke.hip` | is host ROCm usable from a plain process at all (count, alloc, copy) |
| `pcie_bw.hip` | host→device bandwidth at the engine's exact slab size, pinned vs pageable |
| `pcie_probe.hip` | the size sweep — where throughput collapses as transfers shrink |
```bash
scripts/pcie-probe.sh # guard, build, topology, measure
scripts/pcie-probe.sh --device 0 --device 1
scripts/pcie-probe.sh --build-only # compile, never touch the cards
```
## The law: never benchmark beside a live service
The guard runs **before anything is built or run** and refuses when the GPU is
occupied — `gpu_busy_percent` above `PCIE_PROBE_MAX_BUSY` (default 5 %) or any
process holding `/dev/kfd`. Refusal is exit `3`, and it stops nothing.
Auto-stopping a production endpoint is not this tool's decision to make, and a
measurement taken as noise beside serving traffic is worthless anyway: it is
both disruptive and wrong. Take the service down deliberately, or pass `--force`
and mean it. `tests/pcie-probe.bats` pins that the refusal happens before the
build step, so a contended box cannot be measured by accident.
## Reading the numbers
The reference is the **link**, printed as a topology block from sysfs
(`current_link_width` / `current_link_speed` against `max_*`): a card trained to
fewer lanes or a lower generation than it is wired for caps everything
downstream, and no engine change will recover it.
Then the sweep table, whose first row is the engine's actual expert-slab size —
throughput at *that* size is what matters, not the big-transfer figure:
- Small transfers reach the trained-link ceiling → the link is healthy, the
engine's read path (fetch granularity, pipelining, pinning) is the limit.
- Small transfers stall far below a large-transfer figure → the wall is the
link or the IOMMU path, not the engine.
- Pinned versus pageable columns apart means pinning is load-bearing; together
means the copies are already pinned by someone else.
Measure one device at a time (`--device`). A run that lets the runtime pick
several devices cannot attribute bandwidth to a card at all.
## The trap carried in the sources
`hipGetDeviceProperties` **segfaults** on the ROCm build these probes were
written against, while `hipGetDeviceCount` works fine and reports every device.
None of the three sources calls it; device identity comes from sysfs instead.
The setvbuf call in `hip_smoke.hip` is a fossil of discovering that — an
unflushed stdout hid the crash point for an afternoon. Keep both facts intact
when touching these files.
## Exit status and seams
`0` measured · `2` usage or no sources · `3` refused, GPU busy · `4` no HIP
toolchain or build failed. A client box without the SDK exits `4` loudly rather
than reporting an empty success; the probes belong on the box with the cards.
Test seams (all documented in the script header): `PCIE_PROBE_SRC`,
`PCIE_PROBE_BUILD`, `PCIE_PROBE_SYSFS`, `PCIE_PROBE_KFD`, `PCIE_PROBE_HIPCC`,
`PCIE_PROBE_MAX_BUSY` — the sysfs/kfd paths are what make a refusal testable on
a machine with no cards, which is the only honest way to test a refusal.
Measured campaign numbers are not kept here; they belong with the endpoint they
were taken on, in the serverAppConfigs LLM runbook vault.
+83
View File
@@ -0,0 +1,83 @@
---
type: reference
title: remote-job — detached jobs that outlive the session
description: Run a command on a remote box so its result survives the connection, the restart, and the agent that started it — the replacement for ssh piped through tail and sed, which reported success on jobs that never finished.
tags:
- ssh
- fleet
- automation
- verification
verified: 2026-10-20
timestamp: 2026-10-20
---
# remote-job
`scripts/remote-job.sh` starts a command **detached on the host**, and reports
what actually happened from files on disk. It exists because
`ssh host 'cmd | tail -f log | sed -n /ready/q'` lied repeatedly during the
worktree campaign.
## Why the pipe lies
Three independent failure modes, all of them structural:
- **The broker kills long-lived ssh.** A job longer than the connection's
patience dies mid-run; the caller sees a closed pipe, not a failure.
- **A readiness pattern can match a log header.** `sed -n /building/q` fires on
a banner printed before the work starts — the job is at line 1 and the caller
already moved on.
- **`sed -n /x/q` exits the instant it matches.** Even when the pattern is
genuinely ready-shaped, the pipeline tears down the remote command it was
watching. Result: green report, dead job.
The only reliable signal is a **completion sentinel written by the job itself**,
read afterwards by a short-lived connection. That is what this is.
## Contract
```bash
remote-job.sh start NAME [--] CMD... # launch detached, return at once
remote-job.sh status [NAME] # one line per job; exit 0 only if all DONE
remote-job.sh wait NAME [SEC] [N] # block until DONE or timeout, then tail N lines
remote-job.sh out NAME [N] # tail a job's output
remote-job.sh tail [NAME] # follow one job, or every job at once
remote-job.sh watch # refreshing status, for a spare pane
```
Job dir `$REMOTE_JOBS_DIR` (default `$HOME/jobs`). Per job: `NAME.out` (stdout+
stderr), `NAME.rc` (exit code), `NAME.done` (sentinel, **written last**).
Exit status of the *report*: `0` all reported jobs finished · `1` unfinished or
timed out · `2` usage. The `rc=` column is the job's own code — a job that
failed is still `DONE rc=7` and `status` exits `0`, because "is it finished" and
"did it work" are different questions a supervisor must be able to ask
separately.
Written for the case where the job **restarts the endpoint the agent itself is
running on**: the ssh connection dies, the session may die, and the answer is
still sitting in `$JOBS` when a session comes back.
## Use over ssh
```bash
ssh dankserver ~/.omp/scripts/remote-job.sh start swap -- \
bash -c 'systemctl --user restart frank-archon && <the health probe that endpoint answers>'
ssh dankserver ~/.omp/scripts/remote-job.sh wait swap 300 20 # short-lived reconnect
```
Each ssh call is short, so there is nothing there to abort, and the second call
can be made from a fresh session on a machine that rebooted underneath it.
## Failure modes
| Event | Result |
|-------|--------|
| Session/connection dies mid-job | job keeps running; `status` from anywhere reports it |
| Command name is wrong | `PENDING (no output yet)`, exit 1 — never a false DONE |
| Job exits non-zero | `DONE rc=N`; the code is data, not the report's status |
| Two jobs, same NAME | the second replaces the first's state files |
| Job dir missing | created on first use |
Pinned by `tests/remote-job.bats` (black-box: output, exit codes, sentinel
ordering — never internals).
+32
View File
@@ -3,3 +3,35 @@
# pkgbase amd-ucode (split pkgname amd-ucode). No module options needed.
amd-ucode
# Hand-installed additions, measured by scripts/pkg-inventory.sh on 2026-10-20 against the
# Calamares install day (baseline 2026-09-07). These stay out of common.pkgs: none of them belong
# on the headless servers. install.sh installs them only where they are missing, so this list
# changes nothing on the running box — it is what makes the next rebuild faithful.
bun
cachyos-gaming-applications
curlftpfs
flatpak
fuse2
fuseiso
ghostty-nautilus
glow
gnome-calculator
gnome-disk-utility
nodejs
npm
obsidian
partitionmanager
plezy
proton-mail-bin
python-yaml
quickshell
shelly-flatpak-backend
sshfs
tmux
ttf-proggyclean-nerd
ttf-terminus-nerd
wine-cachyos
# nirimod-git is an AUR addition deliberately left undeclared: aur.pkgs is read by every host, and
# a niri module must not be built on the servers. See the known gap in omp-docs/tools/packages.md.
+8
View File
@@ -123,6 +123,14 @@ if [ "$MODE" != "check" ] && [ -d "$PKGS_DIR/systemd-user/.config/systemd/user"
for unit in "$PKGS_DIR"/systemd-user/.config/systemd/user/*.service; do
[ -e "$unit" ] || continue
bin="$(sed -n 's/^ExecStart=\([^ ]*\).*/\1/p' "$unit" | head -1)"
# systemd expands specifiers inside ExecStart; this shell does not. Matching the literal
# `%h/…` makes a unit of THIS box look foreign and skips it in silence — which is exactly
# how live-sync.service, the repo's own heartbeat, has been reported "absent on this
# host" on every machine that deployed it. Expand what a user unit can carry; leave any
# other specifier unmatched rather than guess at it. Refs #44.
bin="${bin//%h/$HOME}"
bin="${bin//%u/$(id -un)}"
bin="${bin//%U/$(id -u)}"
if [ -n "$bin" ] && command -v "$bin" >/dev/null 2>&1; then
name="$(basename "$unit")"
# Read the state, not the exit status: `is-enabled` exits 1 for a `linked` unit exactly
+20 -10
View File
@@ -42,17 +42,27 @@ gen() {
echo "> GENERATED by \`scripts/gen-keybinds.sh\` from \`$(basename "$KBD")\` — do not edit by hand."
echo
awk '
# section header: // ─── Name ───
/^[[:space:]]*\/\/[[:space:]]*─+ (.+) ─+[[:space:]]*$/ {
# Section marker: a comment decorated with box-drawing dashes on both sides,
# `// ─── Name ───`. The dash is matched as a LITERAL and removed with gsub — never
# with a quantifier on the multibyte character. Under LC_ALL=C (cron, systemd units,
# agent shells) gawk works byte-wise, `─+` then means "the last byte of ─, repeated",
# and every header vanishes while the script still exits 0. Plain comments stay
# comments: only a dash-decorated one opens a section.
/^[[:space:]]*\/\/[[:space:]]*─/ {
s = $0
sub(/^[[:space:]]*\/\/[[:space:]]*─+ /, "", s)
sub(/ ─+[[:space:]]*$/, "", s)
if (in_table) print ""
print "## " s
print ""
print "| Key | Action |"
print "|-----|--------|"
in_table = 1
sub(/^[[:space:]]*\/\/[[:space:]]*/, "", s)
sub(/[[:space:]]+$/, "", s)
if (s ~ /─ .*─/) {
gsub(/─/, "", s)
gsub(/[[:space:]]+/, " ", s)
sub(/^ /, "", s); sub(/ $/, "", s)
if (in_table) print ""
print "## " s
print ""
print "| Key | Action |"
print "|-----|--------|"
in_table = 1
}
next
}
# bind line: KEY [flags] [hotkey-overlay-title="..."] { action; }
+13 -1
View File
@@ -4,7 +4,15 @@ import re, sys
from pathlib import Path
kb = Path(sys.argv[1])
issues = []
dirs = sorted({p.parent for p in kb.rglob("*.md")})
def kept(p):
# `.AGENTTRASH/` is quarantine, not bundle content: a gate that flags it pushes an
# agent to delete a page that is only waiting for a pruning session with the operator.
return ".AGENTTRASH" not in p.parts
dirs = sorted({p.parent for p in kb.rglob("*.md") if kept(p)})
for d in dirs:
idx = d / "index.md"
if not idx.is_file() and d != kb:
@@ -21,12 +29,16 @@ for d in dirs:
# wikilinks too
wl_names = {m.group(1).strip().rsplit("/", 1)[-1] for m in re.finditer(r"\[\[([^\]|#]+)", text)}
for sib in sorted(d.iterdir()):
if sib.name == ".AGENTTRASH":
continue
if sib.suffix != ".md" or sib.name == "index.md":
continue
if sib.resolve() not in linked and sib.stem not in wl_names:
issues.append(f"{d.relative_to(kb)}/index.md: does not link {sib.name}")
# subdirs of d linked as their index?
for sub in sorted(d.iterdir()):
if sub.name == ".AGENTTRASH":
continue
if sub.is_dir() and (sub / "index.md").is_file():
subidx = (sub / "index.md").resolve()
dirref = sub.resolve()
+22 -2
View File
@@ -12,7 +12,15 @@ from pathlib import Path
kb = Path(sys.argv[1])
violations = []
for p in sorted(kb.rglob("*.md")):
def pages():
"""Markdown pages that belong to the bundle. `.AGENTTRASH/` is a quarantine, not
content: linting a deleted-but-not-yet-pruned page would demand its destruction to
make the gate go green, which is the wrong incentive."""
for p in kb.rglob("*.md"):
if ".AGENTTRASH" not in p.parts:
yield p
for p in pages():
rel = p.relative_to(kb)
text = p.read_text(errors="replace")
lines = text.splitlines()
@@ -38,9 +46,21 @@ for p in sorted(kb.rglob("*.md")):
if not isinstance(fm, dict) or not str(fm.get("type", "")).strip():
violations.append(f"{rel}: frontmatter lacks required 'type:'")
# One work log per bundle, at the bundle root. A `log.md` is a reserved name, but
# reserved does not mean "allowed in every directory": per-directory logs are how a
# bundle ends up with fifty of them, the entry never where the next agent looks, and
# each copy dating the same event differently. A bundle with no log at all is left to
# the maintenance page to nag about; this gate's job is sprawl.
logs = sorted(str(p.relative_to(kb)) for p in pages() if p.name == "log.md")
if len(logs) > 1 or (logs and logs[0] != "log.md"):
violations.append(
"a bundle has one work log, log.md, at the bundle root; found: " + ", ".join(logs)
)
# link integrity: fenced code blocks hold example paths, not live links — strip first.
# Vendored spec is exempt: its example paths are illustrations, not live links.
for p in sorted(kb.rglob("*.md")):
for p in pages():
if str(p.relative_to(kb)) == "references/okf-spec.md":
continue
text = re.sub(r"^```[^\n]*$^```[^\n]*$", "", p.read_text(errors="replace"), flags=re.M)
+126
View File
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# scripts/pcie-probe.sh — is the decode bottleneck the LINK, or the engine's read path?
#
# Instruments live in scripts/probes/: hip_smoke.hip answers "is the host ROCm usable from a plain
# process", pcie_bw.hip measures host→device bandwidth at the engine's real expert-slab size, and
# pcie_probe.hip sweeps sizes so a stall can be attributed to the link rather than to the engine.
# Written during the RDNA4 expert-offload campaign, kept because the same cards are in Dankserver.
#
# LAW — never benchmark beside a live service. The guard below runs BEFORE anything is built or run,
# and it stops nothing: putting a production endpoint down is not this tool's decision to make. When
# you have deliberately decided that contention is acceptable, say so with --force.
#
# Trap carried in the sources: hipGetDeviceProperties SEGFAULTS on this ROCm build even though
# hipGetDeviceCount works fine and reports every device. Nothing here calls it. Keep it that way.
#
# usage: pcie-probe.sh [--device N]... [--build-only] [--force]
#
# --device N measure this device; repeat for each card. Without it the probes run once with the
# environment's own device selection — one shared run cannot attribute bandwidth.
# --build-only compile and stop; never touches the cards.
# --force measure even though the guard says the GPU is busy.
#
# Environment (defaults in parentheses) — the sysfs and kfd paths are overridable so the guard can
# be tested on a machine with no cards, which is the only honest way to test a refusal:
# PCIE_PROBE_SRC probe sources (<repo>/scripts/probes)
# PCIE_PROBE_BUILD build output ($HOME/.cache/pcie-probe)
# PCIE_PROBE_SYSFS drm sysfs root (/sys/class/drm)
# PCIE_PROBE_KFD KFD device node (/dev/kfd)
# PCIE_PROBE_HIPCC compiler (hipcc)
# PCIE_PROBE_MAX_BUSY refusal threshold, percent (5)
#
# Exit status: 0 measured · 2 usage or missing sources · 3 REFUSED, GPU busy · 4 no toolchain or
# build failed.
set -uo pipefail
SRC="${PCIE_PROBE_SRC:-$(dirname "$(realpath "$0")")/probes}"
BUILD="${PCIE_PROBE_BUILD:-$HOME/.cache/pcie-probe}"
SYSFS="${PCIE_PROBE_SYSFS:-/sys/class/drm}"
KFD="${PCIE_PROBE_KFD:-/dev/kfd}"
HIPCC="${PCIE_PROBE_HIPCC:-hipcc}"
MAX_BUSY="${PCIE_PROBE_MAX_BUSY:-5}"
force=0
build_only=0
devices=()
while [[ $# -gt 0 ]]; do
case "$1" in
--device) [[ $# -ge 2 ]] || { echo "pcie-probe: --device needs an argument" >&2; exit 2; }
devices+=("$2"); shift 2 ;;
--build-only) build_only=1; shift ;;
--force) force=1; shift ;;
*) echo "usage: pcie-probe.sh [--device N]... [--build-only] [--force]" >&2; exit 2 ;;
esac
done
mapfile -t sources < <(find "$SRC" -maxdepth 1 -name '*.hip' 2>/dev/null | sort)
if (( ${#sources[@]} == 0 )); then
echo "pcie-probe: no .hip probe sources in $SRC" >&2
exit 2
fi
# ---- the guard, before anything touches the card --------------------------------
if (( ! force )); then
busy=""
if [[ -e "$KFD" ]] && command -v fuser > /dev/null 2>&1; then
holders="$(fuser "$KFD" 2>/dev/null | tr -s '[:space:]' ' ')"
[[ -n "${holders// /}" ]] && busy="KFD in use by:${holders}"
fi
for card in "$SYSFS"/card*; do
[[ -f "$card/device/gpu_busy_percent" ]] || continue
pct="$(cat "$card/device/gpu_busy_percent" 2>/dev/null)"
[[ "$pct" =~ ^[0-9]+$ ]] || continue
(( pct > MAX_BUSY )) && busy="${busy:+$busy; }$(basename "$card") at ${pct}%"
done
if [[ -n "$busy" ]]; then
printf 'pcie-probe: REFUSED — GPU busy (%s)\n' "$busy" >&2
printf ' never benchmark beside a live service. Take the service down deliberately,\n' >&2
printf ' or pass --force if you have decided the contention is acceptable.\n' >&2
exit 3
fi
fi
# ---- build ----------------------------------------------------------------------
if ! command -v "$HIPCC" > /dev/null 2>&1; then
printf 'pcie-probe: no HIP toolchain (%s) — run this on the box with the cards, not here\n' "$HIPCC" >&2
exit 4
fi
mkdir -p "$BUILD"
for src in "${sources[@]}"; do
bin="$BUILD/$(basename "${src%.hip}")"
if ! "$HIPCC" -O2 -o "$bin" "$src" 2>&1 | tail -3; then
printf 'pcie-probe: build failed for %s\n' "$(basename "$src")" >&2
exit 4
fi
done
if (( build_only )); then
printf 'pcie-probe: built %s probe(s) into %s\n' "${#sources[@]}" "$BUILD"
exit 0
fi
# ---- topology: the link the numbers must be read against ------------------------
printf 'pcie-probe host=%s hipcc=%s probes=%s\n' "$(hostname)" "$HIPCC" "${#sources[@]}"
for card in "$SYSFS"/card*; do
[[ -f "$card/device/current_link_width" ]] || continue
printf ' link %-9s current x%s @ %s max x%s @ %s\n' "$(basename "$card")" \
"$(cat "$card/device/current_link_width")" \
"$(cat "$card/device/current_link_speed" 2>/dev/null || echo '?')" \
"$(cat "$card/device/max_link_width" 2>/dev/null || echo '?')" \
"$(cat "$card/device/max_link_speed" 2>/dev/null || echo '?')"
done
# ---- measure, per device --------------------------------------------------------
(( ${#devices[@]} > 0 )) || devices=(default)
for d in "${devices[@]}"; do
if [[ $d == default ]]; then
printf '\n########## engine device selection ##########\n'
else
printf '\n########## device %s ##########\n' "$d"
fi
for name in hip_smoke pcie_bw pcie_probe; do
bin="$BUILD/$name"
[[ -x "$bin" ]] || continue
if [[ $d == default ]]; then "$bin"; else HIP_VISIBLE_DEVICES="$d" "$bin" "$d"; fi
done
done
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env bash
# scripts/pkg-inventory.sh — what does this machine have that packages/*.pkgs does not declare?
#
# install.sh walks desired → installed. This walks installed → declared, and the pair is what
# makes a rebuild honest: a manifest the machine has quietly outgrown reproduces a machine that
# is missing the tools you installed by hand six weeks ago, and nothing notices until the new box
# has no tmux. Refs #51.
#
# The baseline is what separates "you installed this" from "the installer installed this". It is
# read from the pacman log — the install-day transactions, dated at the oldest `[ALPM] installed`
# line — because declaring Calamares furniture in a manifest is bookkeeping for something a
# reinstall provides for free. Rotated logs (pacman.log.N, .N.gz) are read too: on a machine older
# than the rotation window the install day lives only there, and skipping them would report the
# entire base system as hand-installed.
#
# Report (stdout):
# undeclared additions (declare these): N hand-installed, absent from the manifests
# repo (sync dbs): <names> → belongs in packages/<host>.pkgs
# aur (needs a helper): <names> → belongs in packages/aur.pkgs (yay)
# installer-provided, undeclared: N a reinstall supplies these; do not declare
# declared, not installed: N install.sh installs these
# orphans ignored: <names> pacman -Qtdq — never emitted
# flatpak: <app ids> reported only, install.sh installs no flatpaks
#
# --emit prints the additions as manifest lines, ready to fold into packages/. Names are bare
# tokens so install.sh's `sed 's/#.*//'` + first-field parser reads them unchanged.
#
# Exit status: 0 clean · 1 drift (undeclared additions exist) · 2 usage · 3 cannot classify
# (pacman log unreadable or sync databases unavailable — worse to guess than to say so, because
# guessing would dump every package into aur.pkgs).
#
# Environment overrides (tests):
# PKGINV_REPO repo root holding packages/ (default: git toplevel of this script)
# PKGINV_HOST host name (default: $(hostname))
# PKGINV_LOG space-separated pacman logs (default: /var/log/pacman.log + rotated)
# PKGINV_BASELINE YYYY-MM-DD, inclusive (default: oldest install date in the logs)
set -euo pipefail
export LC_ALL=C # comm/sort agreement: pacman's order is not this order
MODE="report"
while [[ $# -gt 0 ]]; do
case "$1" in
--emit) MODE="emit"; shift ;;
-h | --help) sed -n '2,40p' "$0"; exit 0 ;;
*) echo "usage: pkg-inventory.sh [--emit]" >&2; exit 2 ;;
esac
done
REPO="${PKGINV_REPO:-}"
if [[ -z "$REPO" ]]; then
REPO="$(git -C "$(dirname "$(realpath "$0")")" rev-parse --show-toplevel 2>/dev/null || true)"
fi
[[ -n "$REPO" && -d "$REPO/packages" ]] || { echo "pkg-inventory: no packages/ under ${REPO:-<repo>}" >&2; exit 2; }
HOST="${PKGINV_HOST:-$(hostname)}"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
read_manifest() { # same parser install.sh uses, so a name means the same thing to both
sed 's/#.*//' "$1" | tr -d '\r' | awk '{if ($1) print $1}'
}
# ---- installed state ---------------------------------------------------------
# Four queries are the tool's whole interface to pacman. -Sl is the unprivileged sync listing:
# -Sqq asks for root, and a read of the machine must not need sudo. Field 2 of -Sl is the package
# name, and one name can appear in several databases (core/extra/cachyos), hence the sort -u.
pacman -Qqe > "$TMP/explicit"
pacman -Qq > "$TMP/installed"
pacman -Qtdq > "$TMP/orphans" 2>/dev/null || true # exit 1 when the list is empty: no orphans
if ! pacman -Sl > "$TMP/sync.raw" 2>/dev/null || [[ ! -s "$TMP/sync.raw" ]]; then
echo "pkg-inventory: no sync database available (run: sudo pacman -Sy) — repo vs AUR cannot be classified" >&2
exit 3
fi
awk '{print $2}' "$TMP/sync.raw" | sort -u > "$TMP/sync"
# ---- declared desired state --------------------------------------------------
: > "$TMP/declared"
for m in common.pkgs "$HOST.pkgs" aur.pkgs; do
[[ -f "$REPO/packages/$m" ]] || continue
read_manifest "$REPO/packages/$m" >> "$TMP/declared"
done
# ---- baseline: what the installer put here -----------------------------------
logs=()
if [[ -n "${PKGINV_LOG:-}" ]]; then
read -ra logs <<< "$PKGINV_LOG"
else
logs=(/var/log/pacman.log)
for f in /var/log/pacman.log.[0-9]*; do [[ -e "$f" ]] && logs+=("$f"); done
fi
read_logs() {
local f
for f in "${logs[@]}"; do
[[ -r "$f" ]] || continue
case "$f" in *.gz) zcat "$f" ;; *) cat "$f" ;; esac
done
}
# "YYYY-MM-DD name" for every install event the log remembers.
read_logs | sed -nE 's/^\[([0-9-]{10})T[^]]*\] \[ALPM\] installed ([^ ]+) \(.*/\1 \2/p' > "$TMP/events"
[[ -s "$TMP/events" ]] || { echo "pkg-inventory: no install events readable in ${logs[*]} — baseline unknown" >&2; exit 3; }
# Minimum date in a single awk pass. Sorting and then taking the first line — whether with `head`
# or with an awk that exits early — closes the pipe, and pipefail turns sort's SIGPIPE into a dead
# script; a real install-day log is long enough for that to bite (it killed the first real run).
BASELINE="${PKGINV_BASELINE:-$(awk '{ if (m == "" || $1 < m) m = $1 } END { print m }' "$TMP/events")}"
[[ "$BASELINE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || { echo "pkg-inventory: bad PKGINV_BASELINE '$BASELINE' (want YYYY-MM-DD)" >&2; exit 2; }
# Inclusive: packages installed ON the baseline date came with the install too — Calamares keeps
# installing the desktop for minutes after the base transaction.
awk -v d="$BASELINE" '$1 <= d {print $2}' "$TMP/events" | sort -u > "$TMP/base"
for f in explicit installed sync declared base orphans; do sort -u -o "$TMP/$f" "$TMP/$f"; done
# ---- the sets ----------------------------------------------------------------
# additions: explicit, not declared, and not something the installer handed us.
comm -23 "$TMP/explicit" "$TMP/declared" > "$TMP/undeclared"
comm -23 "$TMP/undeclared" "$TMP/base" > "$TMP/additions"
comm -12 "$TMP/undeclared" "$TMP/base" > "$TMP/installside"
comm -12 "$TMP/additions" "$TMP/sync" > "$TMP/add.repo"
comm -23 "$TMP/additions" "$TMP/sync" > "$TMP/add.aur"
comm -23 "$TMP/declared" "$TMP/installed" > "$TMP/missing"
n() { wc -l < "$1" | tr -d ' '; }
if [[ "$MODE" == emit ]]; then
if [[ -s "$TMP/add.repo" ]]; then
printf '# packages/%s.pkgs — additions measured %s (pkg-inventory.sh, baseline %s)\n' \
"$HOST" "$(date +%F)" "$BASELINE"
cat "$TMP/add.repo"
fi
if [[ -s "$TMP/add.aur" ]]; then
printf '# packages/aur.pkgs — additions measured %s (AUR: not in any sync db, install via yay)\n' \
"$(date +%F)"
cat "$TMP/add.aur"
fi
exit 0
fi
printf 'pkg-inventory host=%s baseline=%s repo=%s\n' "$HOST" "$BASELINE" "$REPO"
printf 'counts declared=%s explicit=%s baseline=%s installed=%s\n' \
"$(n "$TMP/declared")" "$(n "$TMP/explicit")" "$(n "$TMP/base")" "$(n "$TMP/installed")"
printf 'undeclared additions (declare these): %s\n' "$(n "$TMP/additions")"
printf ' repo (sync dbs): %s\n' "$(tr '\n' ' ' < "$TMP/add.repo")"
printf ' aur (needs a helper): %s\n' "$(tr '\n' ' ' < "$TMP/add.aur")"
printf 'installer-provided, undeclared (a reinstall provides these): %s\n' "$(n "$TMP/installside")"
# The names, folded: this is the answer to "what did the installer mark explicit that I never
# declared", and it is long — a count alone would force a second run to see it.
# The echo matters: tr eats the final newline, fold preserves that absence, and the next label
# would otherwise be glued to the last folded line.
{ tr '\n' ' ' < "$TMP/installside"; echo; } | fold -s -w 88 | sed 's/^/ /'
printf 'declared, not installed (install.sh installs these): %s\n' "$(tr '\n' ' ' < "$TMP/missing")"
printf 'orphans ignored (pacman -Qtdq): %s\n' "$(tr '\n' ' ' < "$TMP/orphans")"
if command -v flatpak > /dev/null 2>&1 && flatpak list --app --columns=application > "$TMP/flatpak" 2>/dev/null; then
printf 'flatpak (reported only; install.sh installs no flatpaks): %s\n' "$(tr '\n' ' ' < "$TMP/flatpak")"
else
printf 'flatpak: not installed on this host\n'
fi
[[ -s "$TMP/additions" ]] && exit 1
exit 0
+36
View File
@@ -0,0 +1,36 @@
// Minimal HIP smoke test: is the host ROCm usable from a plain process?
#include <cstdio>
#include <cstring>
#include <hip/hip_runtime.h>
int main(void) {
int n = -1;
hipError_t e = hipGetDeviceCount(&n);
printf("hipGetDeviceCount -> %d (%s), count=%d\n",
(int)e, hipGetErrorString(e), n);
if (e != hipSuccess || n <= 0) return 1;
setvbuf(stdout, NULL, _IONBF, 0); /* an earlier crash lost the buffer, hiding the crash point */
// Device properties are deliberately NOT queried here: hipGetDeviceProperties segfaults on the
// ROCm build this probe was written for, even though hipGetDeviceCount above works fine. Card
// identity comes from sysfs anyway — scripts/pcie-probe.sh prints the link topology.
const size_t bytes = 2459136; /* 2.344 MiB: the engine's expert slab */
void *dev = NULL, *host = NULL;
if (hipMalloc(&dev, bytes) != hipSuccess) { printf("hipMalloc failed\n"); return 1; }
if (hipHostMalloc(&host, bytes, hipHostMallocDefault) != hipSuccess) {
printf("hipHostMalloc failed\n"); return 1;
}
memset(host, 0x5a, bytes);
hipEvent_t a, b; hipEventCreate(&a); hipEventCreate(&b);
hipDeviceSynchronize();
hipEventRecord(a, 0);
const int iters = 200;
for (int i = 0; i < iters; i++) hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice);
hipEventRecord(b, 0); hipEventSynchronize(b);
float ms = 0; hipEventElapsedTime(&ms, a, b);
printf("\n %.3f MiB x %d iters in %.2f ms -> %.2f GB/s H2D (pinned)\n",
bytes/1048576.0, iters, ms, (double)bytes*iters/(ms/1000.0)/1e9);
printf(" Gen5 x8 ceiling = 31.5 GB/s\n");
return 0;
}
+64
View File
@@ -0,0 +1,64 @@
// H2D bandwidth at the engine's expert-slab size.
// Deliberately avoids hipGetDeviceProperties: it segfaults on this ROCm build
// (hipGetDeviceCount works fine and reports 3 devices).
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <hip/hip_runtime.h>
static double one(size_t bytes, bool pinned, int iters) {
void *dev = NULL, *host = NULL;
if (hipMalloc(&dev, bytes) != hipSuccess) return -1;
if (pinned) { if (hipHostMalloc(&host, bytes, hipHostMallocDefault) != hipSuccess) return -1; }
else { host = malloc(bytes); }
memset(host, 0x5a, bytes);
hipEvent_t a, b; hipEventCreate(&a); hipEventCreate(&b);
for (int i = 0; i < 3; i++) hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice);
hipDeviceSynchronize();
hipEventRecord(a, 0);
for (int i = 0; i < iters; i++) hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice);
hipEventRecord(b, 0); hipEventSynchronize(b);
float ms = 0; hipEventElapsedTime(&ms, a, b);
double gb = (double)bytes * iters / (ms / 1000.0) / 1e9;
if (!pinned) free(host); else hipHostFree(host);
hipFree(dev); hipEventDestroy(a); hipEventDestroy(b);
return gb;
}
int main(int argc, char **argv) {
setvbuf(stdout, NULL, _IONBF, 0);
int dev = (argc > 1) ? atoi(argv[1]) : 0;
if (hipSetDevice(dev) != hipSuccess) { printf("setDevice(%d) failed\n", dev); return 1; }
printf("device %d selected\n", dev);
size_t sz[] = {2459136UL, 3UL<<20, 16UL<<20, 64UL<<20, 256UL<<20, 1UL<<30};
printf(" %10s | %7s | %7s | %7s\n", "MiB", "pinned", "pagin.", "xfer us");
printf(" %s\n", "-------------------------------------------");
for (int i = 0; i < 6; i++) {
size_t b = sz[i];
int iters = (b >= (1UL<<30)) ? 10 : (b >= (256UL<<20)) ? 20 : 100;
double p = one(b, true, iters);
double g = one(b, false, iters);
// single-transfer latency
void *dv, *hv;
if (hipMalloc(&dv, b) == hipSuccess && hipHostMalloc(&hv, b, hipHostMallocDefault) == hipSuccess) {
memset(hv, 0x5a, b);
hipEvent_t a, c; hipEventCreate(&a); hipEventCreate(&c);
hipMemcpy(dv, hv, b, hipMemcpyHostToDevice); hipDeviceSynchronize();
hipEventRecord(a, 0);
hipMemcpy(dv, hv, b, hipMemcpyHostToDevice);
hipEventRecord(c, 0); hipEventSynchronize(c);
float ms = 0; hipEventElapsedTime(&ms, a, c);
printf(" %10.3f | %7.2f | %7.2f | %7.1f\n", b/1048576.0, p, g, ms*1000.0);
hipHostFree(hv); hipFree(dv);
} else {
printf(" %10.3f | %7.2f | %7.2f | (alloc failed)\n", b/1048576.0, p, g);
}
}
printf("\n 2.344 MiB is the engine's exact expert slab size.\n");
printf(" Gen5 x8 ceiling = 31.5 GB/s per direction.\n");
return 0;
}
+87
View File
@@ -0,0 +1,87 @@
// HIP adaptation of the pcie-link-capability-probe.
// Question: is the decode bottleneck the LINK, or the engine's read PATH?
// Measure H2D at the engine's actual expert slab size (2.344 MiB) and across
// a size sweep. If 2.344 MiB copies reach ~31 GB/s, the link is healthy and
// the engine is leaving bandwidth on the table. If they stall near 10 GB/s,
// the link is the wall.
//
// build: hipcc -O2 -o pcie_probe pcie_probe.hip (scripts/pcie-probe.sh does this and guards the run)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <hip/hip_runtime.h>
#define CK(x) do { hipError_t e = (x); if (e != hipSuccess) { \
fprintf(stderr, "HIP %s (%s:%d)\n", hipGetErrorString(e), __FILE__, __LINE__); \
return 1; } } while (0)
static double ms_between(hipEvent_t a, hipEvent_t b) {
float ms = 0; hipEventElapsedTime(&ms, a, b); return (double)ms;
}
static int bench_size(size_t bytes) {
const int iters = bytes >= (1UL<<30) ? 20
: bytes >= (256UL<<20) ? 30
: bytes >= (16UL<<20) ? 60 : 150;
void *dev = NULL, *host = NULL, *pageable = NULL;
hipStream_t s = NULL;
CK(hipMalloc(&dev, bytes));
CK(hipHostMalloc(&host, bytes, hipHostMallocDefault)); // PINNED, like the engine's slabs
memset(host, 0x5a, bytes);
CK(hipStreamCreate(&s));
hipEvent_t a, b; CK(hipEventCreate(&a)); CK(hipEventCreate(&b));
for (int i = 0; i < 3; i++) CK(hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice));
CK(hipDeviceSynchronize());
// sync copy
CK(hipEventRecord(a, 0));
for (int i = 0; i < iters; i++) CK(hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice));
CK(hipEventRecord(b, 0)); CK(hipEventSynchronize(b));
double h2d = (double)bytes * iters / (ms_between(a,b)/1000.0) / 1e9;
// async copy on a stream (the engine's fill pipeline shape)
CK(hipEventRecord(a, 0));
for (int i = 0; i < iters; i++) CK(hipMemcpyAsync(dev, host, bytes, hipMemcpyHostToDevice, s));
CK(hipEventRecord(b, 0)); CK(hipEventSynchronize(b));
double h2da = (double)bytes * iters / (ms_between(a,b)/1000.0) / 1e9;
// single-transfer latency at this size
CK(hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice));
CK(hipDeviceSynchronize());
CK(hipEventRecord(a, 0));
CK(hipMemcpy(dev, host, bytes, hipMemcpyHostToDevice));
CK(hipEventRecord(b, 0)); CK(hipEventSynchronize(b));
double one_us = ms_between(a,b) * 1000.0;
// pageable, to see whether pinning is load-bearing here
pageable = malloc(bytes); memset(pageable, 0x5a, bytes);
CK(hipMemcpy(dev, pageable, bytes, hipMemcpyHostToDevice));
CK(hipEventRecord(a, 0));
for (int i = 0; i < iters; i++) CK(hipMemcpy(dev, pageable, bytes, hipMemcpyHostToDevice));
CK(hipEventRecord(b, 0)); CK(hipEventSynchronize(b));
double pag = (double)bytes * iters / (ms_between(a,b)/1000.0) / 1e9;
printf(" %8.3f MiB | %6.2f | %6.2f | %8.1f us | %6.2f\n",
bytes/1048576.0, h2d, h2da, one_us, pag);
free(pageable); hipHostFree(host); hipFree(dev);
hipEventDestroy(a); hipEventDestroy(b); hipStreamDestroy(s);
return 0;
}
int main(int argc, char **argv) {
int dev_id = 0;
if (argc > 1) dev_id = atoi(argv[1]);
// No hipGetDeviceProperties here: it segfaults on this ROCm build while hipGetDeviceCount is
// fine, and the link topology these numbers must be read against comes from sysfs, which
// scripts/pcie-probe.sh prints. Only the device index is needed to pick what to measure.
printf("device %d (PCI bus from sysfs, see the topology block above)\n", dev_id);
printf(" size | H2D | async | 1x xfer | pageable [GB/s]\n");
printf(" %s\n", "-----------------------------------------------------------");
size_t sz[] = {2459136UL, /* 2.344 MiB: the engine's exact expert slab */
3UL<<20, 16UL<<20, 64UL<<20, 256UL<<20, 1UL<<30, 2UL<<30};
for (int i = 0; i < 7; i++) if (bench_size(sz[i])) return 1;
printf("\nGen5 x8 ceiling = 31.5 GB/s per direction\n");
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# scripts/remote-job.sh — a detached job whose result outlives the session that started it
#
# Why this exists rather than `ssh host 'cmd | tail -f | sed -n /ready/q'`, which lied about success
# repeatedly, for three structural reasons — all of them the pipe's fault: the broker aborts
# long-lived ssh; a readiness pattern can match a log *header* before the work has begun; and
# `sed -n /x/q` exits the instant it matches, so the supervisor reports green on a job that never
# finished.
#
# Here the job runs detached on the box it is invoked on (over ssh, or locally). It writes NAME.out,
# then NAME.rc, then touches NAME.done LAST. Status is then a cheap read of a directory: no
# long-lived connection to lose, and the answer survives the session, the terminal, and the agent —
# including the case where the job restarts the very service the agent is running on.
#
# remote-job.sh start NAME [--] CMD... launch detached, return at once
# remote-job.sh status [NAME] one line per job
# remote-job.sh wait NAME [SECONDS] [N] block until DONE or timeout, then tail N lines
# remote-job.sh out NAME [LINES] print the tail of a job's output
# remote-job.sh tail [NAME] follow one job, or every job at once
# remote-job.sh watch refreshing status, for a spare pane
#
# Job directory: $REMOTE_JOBS_DIR, default $HOME/jobs. Ship this file to the host and run it there;
# nothing about it is box-specific, which is the point.
#
# Exit status: 0 when every job reported is finished · 1 when something is unfinished or a wait
# timed out · 2 usage. A job that FAILED still counts as finished for `status`: "is it done" and
# "did it work" are different questions, and the second one is the rc= column.
set -uo pipefail
JOBS="${REMOTE_JOBS_DIR:-$HOME/jobs}"
mkdir -p "$JOBS"
case "${1:-}" in
start)
[[ $# -ge 2 ]] || { echo "usage: remote-job.sh start NAME [--] CMD..." >&2; exit 2; }
name="$2"; shift 2
[[ "${1:-}" == "--" ]] && shift
[[ $# -ge 1 ]] || { echo "remote-job: start needs a command for '$name'" >&2; exit 2; }
# Clear the previous run entirely: a stale .out would make the new run's line count a lie.
rm -f "$JOBS/$name.out" "$JOBS/$name.rc" "$JOBS/$name.done"
# setsid leaves the invoking process group, so a dropped ssh or a killed agent takes nothing
# down with it. rc lands BEFORE the sentinel: a waiter must never see DONE with no exit code.
JOBS="$JOBS" setsid nohup bash -c '
name="$1"; shift
"$@" > "$JOBS/$name.out" 2>&1
echo $? > "$JOBS/$name.rc"
touch "$JOBS/$name.done"
' _ "$name" "$@" < /dev/null > /dev/null 2>&1 &
printf 'started %s\n' "$name"
;;
status)
if [[ -n "${2:-}" ]]; then
names="$2"
else
names=$(cd "$JOBS" && ls *.out 2>/dev/null | sed 's/\.out$//' | sort)
fi
pending=0
for n in $names; do
if [[ -f "$JOBS/$n.done" ]]; then
printf ' %-20s DONE rc=%s %s lines\n' "$n" \
"$(cat "$JOBS/$n.rc" 2>/dev/null)" "$(wc -l < "$JOBS/$n.out" 2>/dev/null | tr -d ' ')"
elif [[ -f "$JOBS/$n.out" ]]; then
printf ' %-20s RUN %s lines so far\n' "$n" "$(wc -l < "$JOBS/$n.out" 2>/dev/null | tr -d ' ')"
pending=1
else
printf ' %-20s PENDING (no output yet)\n' "$n"
pending=1
fi
done
if [[ -z "$names" ]]; then echo " (no jobs)"; exit 0; fi
exit "$pending"
;;
wait)
[[ $# -ge 2 ]] || { echo "usage: remote-job.sh wait NAME [SECONDS] [TAIL]" >&2; exit 2; }
name="$2"; limit="${3:-3600}"; lines="${4:-25}"; waited=0
while [[ ! -e "$JOBS/$name.done" ]] && (( waited < limit )); do
sleep 1; waited=$((waited + 1))
done
if [[ -e "$JOBS/$name.done" ]]; then
printf 'DONE %s rc=%s\n' "$name" "$(cat "$JOBS/$name.rc" 2>/dev/null)"; rc=0
else
printf 'TIMEOUT %s after %ss\n' "$name" "$waited"; rc=1
fi
tail -n "$lines" "$JOBS/$name.out" 2>/dev/null
exit "$rc"
;;
out)
[[ $# -ge 2 ]] || { echo "usage: remote-job.sh out NAME [LINES]" >&2; exit 2; }
tail -n "${3:-40}" "$JOBS/$2.out" 2>/dev/null
;;
tail)
# Multiplexed follow: no NAME means every job at once, so one pane shows the whole harness.
if [[ -n "${2:-}" ]]; then exec tail -f "$JOBS/$2.out"; fi
exec tail -f "$JOBS"/*.out
;;
watch)
while true; do
clear 2>/dev/null || true
date '+%H:%M:%S'
"$0" status
sleep 5
done
;;
*)
echo "usage: remote-job.sh {start|status|wait|out|tail|watch} ..." >&2
exit 2
;;
esac
+16
View File
@@ -136,3 +136,19 @@ systemd_unit() {
[[ "$output" == *"SKIP"*otherbox.service* ]]
! grep -q enable "$STUB_LOG"
}
@test "a unit whose ExecStart uses a systemd specifier still resolves here" {
# `%h` is expanded by systemd, not by the shell, so matching the literal string makes a
# unit belonging to THIS box look foreign and skip it in silence. The victim is this repo's
# own heartbeat: `ExecStart=%h/.omp/scripts/live-sync.sh`, skipped on every box. Refs #44.
mkdir -p "$HOME/.local/bin"
printf '#!/bin/sh\ntrue\n' > "$HOME/.local/bin/helper"
chmod +x "$HOME/.local/bin/helper"
systemd_unit heartbeat.service '%h/.local/bin/helper'
printf 'linked\n' > "$UNIT_STATE"
run "$REPO/scripts/deploy.sh"
[ "$status" -eq 0 ]
[[ "$output" == *"heartbeat.service (already linked"* ]]
grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG"
! grep -q -- '--now' "$STUB_LOG"
}
+10
View File
@@ -48,6 +48,16 @@ teardown() {
[[ "$output" != *"a plain comment"* ]]
}
@test "section headers survive a C locale, where awk reads bytes instead of characters" {
# Cron, systemd units and agent shells get LC_ALL=C while Frank's terminal does not.
# A matcher that puts a quantifier on a multibyte character then loses EVERY section
# header and prints a sheet with no sections at all — silently, exit status 0.
run env LC_ALL=C "$GEN" "$TMP/keybinds.kdl"
[[ "$output" == *"## Applications"* ]]
[[ "$output" == *"## Screenshots"* ]]
[[ "$output" != *"a plain comment"* ]]
}
@test "row count matches bind count" {
run "$GEN" "$TMP/keybinds.kdl"
local rows
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bats
# tests/kb-coverage.bats — black-box tests for scripts/kb-coverage.py
#
# Contract: exit 0 when every directory of a knowledge bundle has an index.md and each
# index links its siblings. Observable surface: stdout lines and exit status.
setup() {
CHECK="$BATS_TEST_DIRNAME/../scripts/kb-coverage.py"
KB="$(mktemp -d)/kb"
mkdir -p "$KB/sub"
printf '# Index\n\n* [Good](good.md)\n* [Sub](sub/)\n' > "$KB/index.md"
printf '# Sub\n\n* [Deep](deep.md)\n' > "$KB/sub/index.md"
printf 'body\n' > "$KB/good.md"
printf 'body\n' > "$KB/sub/deep.md"
}
teardown() { rm -rf "$(dirname "$KB")"; }
@test "a bundle whose directories index their contents passes" {
run python3 "$CHECK" "$KB"
[ "$status" -eq 0 ]
[[ "$output" == *"0 issue(s)"* ]]
}
@test "a directory nobody indexes is reported" {
mkdir "$KB/stray"
printf 'body\n' > "$KB/stray/thing.md"
run python3 "$CHECK" "$KB"
[ "$status" -ne 0 ]
[[ "$output" == *"stray: no index.md"* ]]
}
@test "an index that forgets a sibling page is reported" {
printf 'body\n' > "$KB/orphan.md"
run python3 "$CHECK" "$KB"
[ "$status" -ne 0 ]
[[ "$output" == *"does not link orphan.md"* ]]
}
@test "the quarantine trash directory is not part of the bundle" {
# Pages deleted from the KB wait in .AGENTTRASH/ for a pruning session with Frank.
# Requiring an index there, or listing its contents, would push an agent to delete
# the quarantined copy just to make the gate green.
mkdir -p "$KB/.AGENTTRASH"
printf 'retired page\n' > "$KB/.AGENTTRASH/old-page.md"
run python3 "$CHECK" "$KB"
[ "$status" -eq 0 ]
[[ "$output" == *"0 issue(s)"* ]]
}
+29
View File
@@ -91,6 +91,35 @@ EOF
[ "$status" -eq 2 ]
}
@test "a second work log in a subdirectory is reported" {
# One bundle, one work log. Per-directory logs are how a bundle ends up with fifty
# of them: the entry is never where the next agent looks, and each copy re-dates the
# same event on its own.
printf -- '---\ntype: changelog\n---\n## 2026-01-01\n- created a log\n' > "$KB/log.md"
printf -- '---\ntype: changelog\n---\n## 2026-01-02\n- also a log\n' > "$KB/sub/log.md"
run bash "$LINT" "$KB"
[ "$status" -ne 0 ]
[[ "$output" == *"one work log"*"at the bundle root"* ]]
[[ "$output" == *"sub/log.md"* ]]
}
@test "a work log that is not at the bundle root is reported" {
printf -- '---\ntype: changelog\n---\n## 2026-01-02\n- misplaced log\n' > "$KB/sub/log.md"
run bash "$LINT" "$KB"
[ "$status" -ne 0 ]
[[ "$output" == *"one work log"*"at the bundle root"* ]]
}
@test "the quarantine trash directory is not part of the bundle" {
# Deleted pages wait in .AGENTTRASH/ for a pruning session with Frank. Holding them
# to frontmatter and one-log rules would push an agent to delete history to go green.
mkdir -p "$KB/.AGENTTRASH"
printf 'no frontmatter, and a second log besides\n' > "$KB/.AGENTTRASH/log.md"
run bash "$LINT" "$KB"
[ "$status" -eq 0 ]
[[ "$output" == *"0 violation(s)"* ]]
}
@test "the shipped knowledge base itself is conformant" {
# The tests above exercise the linter against fixtures; nothing else in the
# suite ever points it at the real bundle, so a page added without frontmatter
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bats
# tests/pcie-probe.bats — black-box tests for scripts/pcie-probe.sh (Refs #53)
#
# Contract: measure host→device bandwidth on an RDNA4 box, and refuse to measure while the GPU is
# in use. Fleet law: never benchmark beside a live service — so the guard runs BEFORE anything is
# built or run, and the tool stops nothing and starts nothing (auto-stopping a production service is
# the thing it must not do unasked).
#
# Exit status: 0 measured · 2 usage · 3 refused (GPU busy) · 4 no toolchain / build failed.
#
# hipcc and the compiled probes are stubbed: this box has no HIP SDK (it is a client laptop) and a
# real measurement belongs on a machine with the cards, beside no running model.
setup() {
TEST_TMP="$(mktemp -d)"
SCRIPT="$BATS_TEST_DIRNAME/../scripts/pcie-probe.sh"
SRC="$TEST_TMP/src"
BUILD="$TEST_TMP/build"
SYSFS="$TEST_TMP/drm"
BIN="$TEST_TMP/bin"
mkdir -p "$SRC" "$BUILD" "$BIN" "$SYSFS/card0/device" "$SYSFS/card1/device"
# Fixtures stand in for the real sysfs files the driver reads.
printf '8\n' > "$SYSFS/card0/device/current_link_width"
printf '32.0 GT/s PCIe\n' > "$SYSFS/card0/device/current_link_speed"
printf '16\n' > "$SYSFS/card0/device/max_link_width"
printf '32.0 GT/s PCIe\n' > "$SYSFS/card0/device/max_link_speed"
printf '0\n' > "$SYSFS/card0/device/gpu_busy_percent"
printf '8\n' > "$SYSFS/card1/device/current_link_width"
printf '32.0 GT/s PCIe\n' > "$SYSFS/card1/device/current_link_speed"
printf '16\n' > "$SYSFS/card1/device/max_link_width"
printf '32.0 GT/s PCIe\n' > "$SYSFS/card1/device/max_link_speed"
printf '0\n' > "$SYSFS/card1/device/gpu_busy_percent"
# Probe sources: the real .hip files are shipped by the script's own directory, so the fixtures
# only need to exist for the build step to be invoked.
: > "$SRC/pcie_probe.hip"; : > "$SRC/pcie_bw.hip"; : > "$SRC/hip_smoke.hip"
# Stub toolchain: records that it was called, and emits a probe binary that records that IT ran.
cat > "$BIN/hipcc" <<'STUB'
#!/usr/bin/env bash
[ "${STUB_HIPCC_FAILS:-0}" = 1 ] && { echo "stub: no matching GPU target" >&2; exit 1; }
out=""
while [ $# -gt 0 ]; do [ "$1" = "-o" ] && out="$2"; shift; done
[ -n "$out" ] || exit 2
: > "$PCIE_PROBE_BUILD/.built"
{ echo '#!/usr/bin/env bash'
echo ': > "$PCIE_PROBE_BUILD/.ran"'
echo 'echo "PROBE RAN dev=${HIP_VISIBLE_DEVICES:-none} bin=$(basename "$0")"'
echo 'echo " slab 2.344 MiB: 31.4 GB/s"'
} > "$out"
chmod +x "$out"
STUB
cat > "$BIN/fuser" <<'STUB'
#!/usr/bin/env bash
[ -n "${STUB_KFD_PIDS:-}" ] && printf '%s\n' $STUB_KFD_PIDS
exit 0
STUB
chmod +x "$BIN/hipcc" "$BIN/fuser"
export PATH="$BIN:$PATH" PCIE_PROBE_SRC="$SRC" PCIE_PROBE_BUILD="$BUILD" \
PCIE_PROBE_SYSFS="$SYSFS" PCIE_PROBE_KFD="$TEST_TMP/kfd" PCIE_PROBE_HIPCC="$BIN/hipcc"
: > "$TEST_TMP/kfd"
}
teardown() { rm -rf "$TEST_TMP"; }
@test "a missing HIP toolchain is a loud failure, not a silent skip" {
run env PCIE_PROBE_HIPCC="$TEST_TMP/nope-hipcc" "$SCRIPT"
[ "$status" -eq 4 ]
[[ "$output" == *"hip"* ]]
[ ! -e "$BUILD/.built" ]
}
@test "a failed build is reported as a failure" {
run env STUB_HIPCC_FAILS=1 "$SCRIPT"
[ "$status" -eq 4 ]
[[ "$output" == *"build"* ]]
}
@test "a GPU held by another process refuses the measurement before anything is built" {
# Order is the contract: build-and-then-ask would already have loaded the card beside a live
# service. The .built marker proves the guard ran first.
run env STUB_KFD_PIDS="4242" "$SCRIPT"
[ "$status" -eq 3 ]
[[ "$output" == *"busy"* ]]
[ ! -e "$BUILD/.built" ]
}
@test "a busy GPU percent refuses too" {
printf '91\n' > "$SYSFS/card0/device/gpu_busy_percent"
run "$SCRIPT"
[ "$status" -eq 3 ]
[[ "$output" == *"card0"* ]]
[ ! -e "$BUILD/.built" ]
}
@test "--force overrides the guard for a deliberately contended measurement" {
run env STUB_KFD_PIDS="4242" "$SCRIPT" --force
[ "$status" -eq 0 ]
[ -e "$BUILD/.ran" ]
}
@test "an idle GPU builds and runs the probe" {
run "$SCRIPT"
[ "$status" -eq 0 ]
[ -e "$BUILD/.built" ]
[ -e "$BUILD/.ran" ]
[[ "$output" == *"PROBE RAN"* ]]
[[ "$output" == *"31.4 GB/s"* ]] # probe output passes through untouched
}
@test "--build-only compiles without touching the card" {
run "$SCRIPT" --build-only
[ "$status" -eq 0 ]
[ -e "$BUILD/.built" ]
[ ! -e "$BUILD/.ran" ]
}
@test "the link topology is reported from sysfs, current against max" {
run "$SCRIPT"
[[ "$output" == *"card0"*"x8"*"32.0 GT/s"* ]]
[[ "$output" == *"max x16"* ]]
}
@test "each requested device is measured separately" {
# One shared run cannot attribute bandwidth to a card; the whole point is per-card numbers.
run "$SCRIPT" --device 0 --device 1
[ "$status" -eq 0 ]
[[ "$output" == *"dev=0"* ]]
[[ "$output" == *"dev=1"* ]]
}
@test "no probe sources is a usage-level error, not an empty success" {
rm -f "$SRC"/*.hip
run "$SCRIPT"
[ "$status" -ne 0 ]
[[ "$output" == *"probe"* ]]
}
@test "an unknown flag is a usage error" {
run "$SCRIPT" --frobnicate
[ "$status" -eq 2 ]
}
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/env bats
# tests/pkg-inventory.bats — black-box tests for scripts/pkg-inventory.sh (Refs #51)
#
# Contract: `install.sh` installs desired-minus-installed from packages/*.pkgs. This tool walks
# the other direction — installed-minus-declared — because a manifest the machine has quietly
# outgrown cannot reproduce the machine on a rebuild. Only stdout, stderr and the exit status are
# observed: 0 no drift, 1 drift (hand-installed packages undeclared), 2 usage, 3 sync databases
# unavailable so repo/AUR classification is impossible.
#
# Every pacman/flatpak call hits a stub: nothing here may query the real package database, and a
# real pacman on PATH would make the fixture answers depend on the workstation.
setup() {
TEST_TMP="$(mktemp -d)"
SCRIPT="$BATS_TEST_DIRNAME/../scripts/pkg-inventory.sh"
REPO="$TEST_TMP/repo"
mkdir -p "$REPO/packages" "$TEST_TMP/bin"
# --- manifests: common + host(box) + aur ------------------------------------
cat > "$REPO/packages/common.pkgs" <<'EOF'
# common desired state
ghostty
zoxide
vlc # declared, deliberately NOT installed below
EOF
cat > "$REPO/packages/box.pkgs" <<'EOF'
neovim
EOF
cat > "$REPO/packages/aur.pkgs" <<'EOF'
typora # AUR-only desired state
EOF
# --- pacman log: install day 2026-01-01, hand installs from 2026-03 ----------
cat > "$TEST_TMP/pacman.log" <<'EOF'
[2026-01-01T10:00:00-0800] [PACMAN] Running 'pacman --sysroot /tmp/calamares-root-x -S base'
[2026-01-01T10:00:01-0800] [ALPM] installed filesystem (2026.01.01-1)
[2026-01-01T10:00:02-0800] [ALPM] installed base (3-1)
[2026-01-01T10:00:03-0800] [ALPM] installed libfoo (1.0-1)
[2026-03-04T09:00:00-0800] [ALPM] installed bun (1.2-1)
[2026-03-04T09:30:00-0800] [ALPM] upgraded filesystem (2026.01.01-1 -> 2026.03.01-1)
[2026-03-05T09:00:00-0800] [ALPM] installed nirimod-git (r7-1)
[2026-03-06T09:00:00-0800] [ALPM] removed base (3-1)
EOF
# --- installed state --------------------------------------------------------
# ghostty/zoxide/neovim/typora declared. libfoo: explicit, arrived with the installer.
# bun/nirimod-git: installed by hand afterwards, undeclared. python-hatchling: orphan.
printf '%s\n' base bun filesystem ghostty libfoo neovim nirimod-git typora zoxide > "$TEST_TMP/local-all"
printf '%s\n' bun ghostty libfoo neovim nirimod-git typora zoxide > "$TEST_TMP/local-explicit"
printf '%s\n' python-hatchling > "$TEST_TMP/orphans"
# nirimod-git is deliberately ABSENT here: no sync db can serve it, which is the fact that
# routes it to aur.pkgs in the test below.
printf '%s\n' bun ghostty neovim typora zoxide libfoo vlc python-hatchling base filesystem > "$TEST_TMP/sync-all"
# Four flag combinations are the script's whole interface to the package manager; the stub
# answers those and refuses anything else, so an accidental new query fails loudly.
cat > "$TEST_TMP/bin/pacman" <<'STUB'
#!/usr/bin/env bash
case "$1" in
-Sl) [ "${STUB_SYNC_EMPTY:-0}" = 1 ] && exit 1
sed 's/^/syncdb /' "$SYNC_ALL" ;;
-Qqe) cat "$LOCAL_EXPLICIT" ;;
-Qtdq) cat "$ORPHANS"; [ -s "$ORPHANS" ] ;;
-Qq) cat "$LOCAL_ALL" ;;
*) echo "stub pacman: unhandled args: $*" >&2; exit 64 ;;
esac
STUB
cat > "$TEST_TMP/bin/flatpak" <<'STUB'
#!/usr/bin/env bash
[ "${STUB_NO_FLATPAK:-0}" = 1 ] && exit 127
printf '%s\n' com.discordapp.Discord
STUB
chmod +x "$TEST_TMP/bin/pacman" "$TEST_TMP/bin/flatpak"
export PATH="$TEST_TMP/bin:$PATH"
export PKGINV_REPO="$REPO" PKGINV_HOST=box PKGINV_LOG="$TEST_TMP/pacman.log"
export LOCAL_ALL="$TEST_TMP/local-all" LOCAL_EXPLICIT="$TEST_TMP/local-explicit"
export ORPHANS="$TEST_TMP/orphans" SYNC_ALL="$TEST_TMP/sync-all"
}
teardown() { rm -rf "$TEST_TMP"; }
# section RE_START RE_END — the report from RE_START's line up to (not including) RE_END, so an
# assertion proves a name landed in a specific section instead of merely appearing somewhere in the
# report. Start inclusive (the count and the names sit on the label line), terminator exclusive (a
# plain sed range would leak the next section's first line). Anchoring matters: an unanchored start
# runs the range to end-of-output, silently turning every absence assertion into a vacuous pass —
# exactly how four of these tests were passing before this helper was fixed.
section() { printf '%s\n' "$output" | awk -v s="$1" -v e="$2" '$0 ~ e {on=0} $0 ~ s {on=1} on'; }
ADDITIONS() { section "^undeclared additions" "^installer-provided"; }
@test "header names the host and takes the baseline from the oldest install line" {
run "$SCRIPT"
[ "$status" -eq 1 ] # the fixture has drift by design
[[ "$output" == *"pkg-inventory host=box"* ]]
[[ "$output" == *"baseline=2026-01-01"* ]]
}
@test "hand-installed packages are reported as additions" {
run "$SCRIPT"
[[ "$output" == *"undeclared additions (declare these): 2"* ]]
s="$(ADDITIONS)"
[[ "$s" == *bun* ]]
[[ "$s" == *nirimod-git* ]]
}
@test "a package the installer provided is not reported as an addition" {
run "$SCRIPT"
additions="$(ADDITIONS)"
[[ "$additions" != *libfoo* ]]
[[ "$(section "^installer-provided" "^declared,")" == *libfoo* ]]
# The folded block must end in a newline. Without one, the following label is glued onto the
# last folded line and every section after it vanishes from a sliced report.
[[ "$(section "^installer-provided" "^declared,")" != *declared,* ]]
}
@test "AUR-only packages are separated from sync-repo packages" {
run "$SCRIPT"
# nirimod-git is missing from the sync-DB fixture, so only a helper can install it: it must be
# routed to aur.pkgs, not to a pacman line that would fail on a fresh box. Bash `*` crosses
# newlines, so the absence check runs on one line rather than on the whole section.
repo_line="$(section '^ repo ' '^ aur ')"
[[ "$repo_line" == *"repo (sync dbs): bun"* ]]
[[ "$repo_line" != *nirimod-git* ]]
[[ "$(section '^ aur ' '^installer-provided')" == *"aur (needs a helper): nirimod-git"* ]]
}
@test "declared-but-missing packages are listed for install.sh" {
run "$SCRIPT"
[[ "$output" == *"declared, not installed"* ]]
[[ "$(section "^declared," "^orphans")" == *vlc* ]]
}
@test "orphans are reported as ignored and never emitted" {
run "$SCRIPT"
[[ "$output" == *orphans*python-hatchling* ]]
run "$SCRIPT" --emit
[[ "$output" != *python-hatchling* ]]
}
@test "--baseline moves packages installed on that date into the installer set" {
# Inclusive date semantics: pushing the baseline to bun's install date makes bun installer
# furniture, while nirimod-git (the next day) is still a hand install.
run env PKGINV_BASELINE=2026-03-04 "$SCRIPT"
[[ "$output" == *"baseline=2026-03-04"* ]]
s="$(ADDITIONS)"
[[ "$s" != *bun* ]]
[[ "$s" == *nirimod-git* ]]
}
@test "--emit prints lines that survive the manifest parser" {
run "$SCRIPT" --emit
[ "$status" -eq 0 ]
# install.sh reads manifests as `sed 's/#.*//'` then the first field, so an emitted name must
# be a bare token: a stray path or trailing note would be handed to pacman as a package name.
bad="$(printf '%s\n' "$output" | grep -v '^#' | grep -vE '^[A-Za-z0-9@._+-]+$' || true)"
[ -z "$bad" ]
printf '%s\n' "$output" > "$REPO/packages/emitted.pkgs"
names="$(sed 's/#.*//' "$REPO/packages/emitted.pkgs" | awk '{if ($1) print $1}' | sort | tr '\n' ' ')"
[[ "$names" == *bun* ]]
[[ "$names" == *nirimod-git* ]]
[[ "$names" != *ghostty* ]]
[[ "$names" != *libfoo* ]]
[[ "$names" != *vlc* ]]
}
@test "an unknown host gets the common manifest only, so host packages look undeclared" {
# Mirrors install.sh: wrong-host runs are a no-op, not a hazard. Here that means neovim,
# declared only in box.pkgs, counts as an addition when the host is not box.
run env PKGINV_HOST=otherbox "$SCRIPT"
[[ "$(ADDITIONS)" == *neovim* ]]
}
@test "no drift exits 0" {
printf '%s\n' ghostty libfoo zoxide > "$TEST_TMP/local-explicit"
run "$SCRIPT"
[ "$status" -eq 0 ]
[[ "$output" == *"undeclared additions (declare these): 0"* ]]
}
@test "missing sync databases fail loudly rather than misclassifying everything as AUR" {
run env STUB_SYNC_EMPTY=1 "$SCRIPT"
[ "$status" -eq 3 ]
[[ "$output" == *"sync"* ]]
}
@test "flatpak apps are reported separately from pacman packages" {
run "$SCRIPT"
[[ "$output" == *"flatpak"*com.discordapp.Discord* ]]
}
@test "a box without flatpak still reports the pacman inventory" {
run env STUB_NO_FLATPAK=1 "$SCRIPT"
[ "$status" -eq 1 ]
[[ "$output" == *"flatpak"* ]]
[[ "$(ADDITIONS)" == *bun* ]]
}
@test "the baseline is found in a rotated log when the install day has aged out of the live one" {
# pacman rotates its log; without reading the rotated file the whole base system would be
# reported as hand-installed additions on any machine older than the rotation window.
mv "$TEST_TMP/pacman.log" "$TEST_TMP/pacman.log.1"
printf '[2026-10-01T09:00:00-0700] [ALPM] upgraded zoxide (0.9-1 -> 0.10-1)\n' > "$TEST_TMP/pacman.log"
run env PKGINV_LOG="$TEST_TMP/pacman.log $TEST_TMP/pacman.log.1" "$SCRIPT"
[[ "$output" == *"baseline=2026-01-01"* ]]
[[ "$(ADDITIONS)" != *libfoo* ]]
}
@test "a log larger than the pipe buffer still yields the baseline" {
# A real install day writes thousands of install lines. `sort | head -1` under pipefail dies of
# SIGPIPE once that output exceeds the pipe buffer, and no small fixture can show it — the first
# run against a real log is where it surfaced. seq, not yes: yes would SIGPIPE in the fixture.
seq 1 20000 | awk '{print "[2025-05-05T10:00:00-0700] [ALPM] installed pkgfiller (1-1)"}' > "$TEST_TMP/big.log"
cat "$TEST_TMP/big.log" "$TEST_TMP/pacman.log" > "$TEST_TMP/combined.log"
run env PKGINV_LOG="$TEST_TMP/combined.log" "$SCRIPT"
[ "$status" -eq 1 ]
[[ "$output" == *"baseline=2025-05-05"* ]]
}
@test "usage is a distinct exit status from drift" {
run "$SCRIPT" --nope
[ "$status" -eq 2 ]
}
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bats
# tests/remote-job.bats — black-box tests for scripts/remote-job.sh (Refs #53)
#
# Contract: a job runs detached, writes its output, then its exit code, then touches .done LAST.
# Status is then a cheap independent read that survives the session that started the job — the
# whole point is replacing `ssh host 'tail -f | sed'`, which reported success on jobs that had not
# finished. Exit status answers "is everything finished"; the rc= field answers "did it work".
#
# Jobs really run here, against a temp job dir. No fake filesystem state is pre-seeded, because a
# fixture that pre-creates .done proves nothing about the code that is supposed to create it.
setup() {
TEST_TMP="$(mktemp -d)"
SCRIPT="$BATS_TEST_DIRNAME/../scripts/remote-job.sh"
export REMOTE_JOBS_DIR="$TEST_TMP/jobs"
}
teardown() {
# Jobs are detached on purpose; leave nothing behind that could outlive the test run.
pkill -P 1 -f "$TEST_TMP" 2>/dev/null || true
rm -rf "$TEST_TMP"
}
# until_done NAME SECONDS — poll instead of sleeping a fixed amount, so the suite is neither slow
# nor flaky on a loaded machine.
until_done() {
local name="$1" limit="${2:-10}" waited=0
while [[ ! -e "$REMOTE_JOBS_DIR/$name.done" && $waited -lt $((limit * 10)) ]]; do
sleep 0.1; waited=$((waited + 1))
done
[[ -e "$REMOTE_JOBS_DIR/$name.done" ]]
}
@test "start returns immediately while the job is still running" {
run timeout 10 "$SCRIPT" start slow -- sleep 2
[ "$status" -eq 0 ]
[[ "$output" == "started slow" ]]
# A 2-second job must not have blocked a start that is supposed to return at once, and while it
# is still in flight the report must say unfinished — never DONE.
run "$SCRIPT" status slow
[[ "$output" == *RUN* || "$output" == *PENDING* ]]
[ "$status" -eq 1 ]
}
@test "a successful job is reported DONE with its output" {
"$SCRIPT" start ok -- bash -c 'echo marker-4711'
until_done ok
run "$SCRIPT" status ok
[[ "$output" == *"ok"*DONE*rc=0* ]]
[ "$status" -eq 0 ]
run "$SCRIPT" out ok
[[ "$output" == *marker-4711* ]]
}
@test "a failing job's exit code survives into the report" {
# This is the reason the .rc file exists: a supervisor that cannot see the failure code is the
# bug the runner was written to fix.
"$SCRIPT" start boom -- bash -c 'echo it failed here >&2; exit 7'
until_done boom
run "$SCRIPT" status boom
[[ "$output" == *"boom"*DONE*rc=7* ]]
run "$SCRIPT" out boom
[[ "$output" == *"it failed here"* ]]
}
@test "the .done sentinel is never written before the exit code" {
# If .done landed first, a waiter could read an empty rc and call the job successful.
"$SCRIPT" start order -- bash -c 'echo x'
until_done order
[ -f "$REMOTE_JOBS_DIR/order.rc" ]
[ -s "$REMOTE_JOBS_DIR/order.rc" ]
run cat "$REMOTE_JOBS_DIR/order.rc"
[[ "$output" =~ ^[0-9]+$ ]]
}
@test "wait prints DONE with the exit code and a tail of the output" {
"$SCRIPT" start fin -- bash -c 'echo one; echo two'
run "$SCRIPT" wait fin 15
[ "$status" -eq 0 ]
[[ "$output" == *"DONE fin rc=0"* ]]
[[ "$output" == *two* ]]
}
@test "wait gives up on its own and says so" {
"$SCRIPT" start never -- sleep 30
run "$SCRIPT" wait never 1
[ "$status" -eq 1 ]
[[ "$output" == *TIMEOUT*never* ]]
"$SCRIPT" status never | grep -q . # the job is still there, unfinished
}
@test "status distinguishes running from pending from done" {
run "$SCRIPT" status
[[ "$output" == *"(no jobs)"* ]]
[ "$status" -eq 0 ]
"$SCRIPT" start later -- sleep 3
run "$SCRIPT" status later
[[ "$output" == *later* ]]
[[ "$output" == *RUN* || "$output" == *PENDING* ]]
[ "$status" -eq 1 ] # unfinished: a supervisor must be able to branch on this
}
@test "two jobs in flight keep separate state" {
"$SCRIPT" start a -- bash -c 'echo A; exit 0'
"$SCRIPT" start b -- bash -c 'echo B; exit 2'
until_done a && until_done b
run "$SCRIPT" status
[[ "$output" == *"a"*DONE*rc=0* ]]
[[ "$output" == *"b"*DONE*rc=2* ]]
run "$SCRIPT" out a
[[ "$output" == A ]]
run "$SCRIPT" out b
[[ "$output" == B ]]
}
@test "out honours a line limit" {
"$SCRIPT" start many -- bash -c 'seq 1 40'
until_done many
run "$SCRIPT" out many 3
[ "$(printf '%s\n' "$output" | grep -c .)" -eq 3 ]
[[ "$output" == *38* ]] # the tail, not the head
}
@test "an unknown verb is a usage error" {
run "$SCRIPT" frobnicate
[ "$status" -eq 2 ]
[[ "$output" == *usage* ]]
}
@test "the job directory is created on first use" {
[ ! -d "$REMOTE_JOBS_DIR" ]
"$SCRIPT" status
[ -d "$REMOTE_JOBS_DIR" ]
}