Two conflicts, both resolved to trunk, both proven content-free for the running machine before the merge was committed: - `agent/APPEND_SYSTEM.md` — the checkout's dirty copy and trunk differ in one line only: #61's trim of the duplicated one-log reasoning, replaced by the owner pointer. The doctrine text itself is byte-identical on both sides. - `desk-1-home.kdl` — non-comment lines are byte-identical (`diff` on the stripped file: no output), so the deployed modes are unchanged: `3440x1440@180.000` ×3 and `1920x1080@60.001`. What changes is the comment: trunk's carries the measured mode list (`3440x1440@239.998` exists), the reason 180 is deliberate, and the verbatim mode-string law — the live copy still asserted "no 240 Hz mode exists", which the panel contradicts. `niri validate` on the assembled config: valid. Deployment state after this: the checkout is main, and `agent/memories/**` + `agent/managed-skills/**` were committed by the heartbeat commit underneath. Refs #60 #62
This commit is contained in:
@@ -1,3 +1,13 @@
|
||||
# LFS routing for image types. Measured 2026-10-21: this repo tracks ZERO LFS files
|
||||
# (`git lfs ls-files` = 0) — the wallpaper library became live theme state and is gitignored
|
||||
# (omp-docs/tools/dotfiles-deploy.md), so these four rules match nothing today. They are kept
|
||||
# because tracked media is a plausible future and idle filters cost nothing; deleting the file
|
||||
# is a pruning call for a session with Frank, not a cleanup an agent runs alone.
|
||||
#
|
||||
# Do not cite this file as the reason `git-lfs` is installed on a box. The manifest carries its
|
||||
# own justification (packages/common.pkgs), and a tool justified by dead config is a tool nobody
|
||||
# can defend when the config goes.
|
||||
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
+54
-11
@@ -34,24 +34,36 @@ placeholders='changeme|change[-_]|placeholder|redacted|example|sample|dummy|noke
|
||||
# how a gate gets permanently overridden.
|
||||
assign_exempt='(_FILE|_PATH|_DIR|_URL|_ENDPOINT|_SOCKET)[[:space:]]*[:=]|[:=][[:space:]]*["'"'"']?(/|\.\./)'
|
||||
# Fail closed: a gate that cannot read the staged diff must not report success.
|
||||
if ! files=$(git diff --cached --diff-filter=ACM --name-only 2>&1); then
|
||||
#
|
||||
# `-z`, and the list lives in a file. Two traps decide this: git quotes any path it cannot print
|
||||
# as UTF-8 (core.quotePath is on by default), and a quoted name handed back to
|
||||
# `git diff -- <path>` matches nothing — the file is skipped, and a skipped file used to be a
|
||||
# silent pass. Command substitution cannot hold the list either, because $() deletes NUL bytes,
|
||||
# so the names go to a temp file and the loop reads them with a NUL delimiter.
|
||||
TMPD=$(mktemp -d) || TMPD=""
|
||||
if [ -z "$TMPD" ]; then
|
||||
echo "SECRET GATE: cannot create a temp dir — refusing to commit." >&2
|
||||
[ -n "${ALLOW_SECRETS:-}" ] && exit 0
|
||||
exit 1
|
||||
fi
|
||||
trap 'rm -rf "$TMPD"' EXIT
|
||||
if ! git diff --cached --diff-filter=ACM --name-only -z > "$TMPD/files" 2> "$TMPD/err"; then
|
||||
{
|
||||
echo "SECRET GATE: cannot read the staged diff — refusing to commit."
|
||||
printf ' %s\n' "$files"
|
||||
sed 's/^/ /' "$TMPD/err"
|
||||
echo "Override deliberately: ALLOW_SECRETS=1 git commit ..."
|
||||
} >&2
|
||||
[ -n "${ALLOW_SECRETS:-}" ] && exit 0
|
||||
exit 1
|
||||
fi
|
||||
# stderr is captured so the failure branch can quote it; on success it must not
|
||||
# leak a `warning:` line into the file list and have it mistaken for a path.
|
||||
files=$(printf '%s\n' "$files" | grep -vE '^(warning|fatal|error):' || true)
|
||||
[ -z "${files//[[:space:]]/}" ] && exit 0
|
||||
# git's stderr stays out of the name list on purpose: a `warning:` line mistaken for a path
|
||||
# becomes a file that "cannot be read", which is how a gate starts skipping the real ones.
|
||||
[ -s "$TMPD/files" ] || exit 0
|
||||
|
||||
report=""
|
||||
exempt=""
|
||||
count=0
|
||||
while IFS= read -r f; do
|
||||
while IFS= read -r -d '' f; do
|
||||
[ -z "$f" ] && continue
|
||||
# Self-exemption, deliberately an exact filename list and not `tests/`: the
|
||||
# gate's fixtures must contain credential-shaped strings or there is nothing to
|
||||
@@ -67,17 +79,48 @@ while IFS= read -r f; do
|
||||
# gate refused the redaction of the password it exists to catch.
|
||||
added=$(printf '%s\n' "$diff" | grep -E '^\+' | grep -vE '^\+\+\+' | sed 's/^+//' || true)
|
||||
[ -z "$added" ] && continue
|
||||
hits=$(printf '%s\n' "$added" | grep -Ei "$shapes" || true)
|
||||
# Shape hits are judged on the MATCHED TOKEN, not on the whole line. Documentation about
|
||||
# credentials quotes vendor placeholders — AWS publishes an all-zeros access-key id that ends
|
||||
# in EXAMPLE for exactly this purpose — and
|
||||
# a gate that blocks prose over an inert example gets worked around until it means nothing:
|
||||
# the live-sync heartbeat failed three ticks on 2026-10-21 because a managed skill page quotes
|
||||
# exactly that id. A line is exempted only when EVERY shape on it is inert, so one live token
|
||||
# sharing the file with a documented fake still blocks.
|
||||
hits=$(printf '%s\n' "$added" | while IFS= read -r line; do
|
||||
live=0
|
||||
while IFS= read -r m; do
|
||||
# The shape regexes carry fixed quantifiers, so the match they return is TRUNCATED:
|
||||
# `AKIA` + exactly 16 characters stops inside the word EXAMPLE and the placeholder
|
||||
# disappears from what is judged. Judge the match plus what follows it instead.
|
||||
ctx=$(printf '%s' "$line" | awk -v m="$m" '{ i = index($0, m); print (i > 0 ? substr($0, i, length(m) + 8) : m) }')
|
||||
printf '%s' "$ctx" | grep -Eiq 'EXAMPLE|X{4,}|Y{4,}|Z{4,}|_{4,}|REDACTED|CHANGEME|PLACEHOLDER' || live=1
|
||||
done < <(printf '%s\n' "$line" | grep -Eio "$shapes" || true)
|
||||
[ "$live" = 1 ] && printf '%s\n' "$line"
|
||||
done)
|
||||
if [ "${f##*.}" != "md" ]; then
|
||||
assigns=$(printf '%s\n' "$added" | grep -Ei "$assign" | grep -Ev "$assign_exempt" | grep -Eiv "$placeholders" || true)
|
||||
# The placeholder filter reads the VALUE, never the whole line. A trailing comment that
|
||||
# says `# example` says nothing about the token the same line assigns; scoping the filter
|
||||
# to what follows the separator is what keeps `API_TOKEN=<real> # placeholder` a block.
|
||||
assigns=$(printf '%s\n' "$added" | grep -Ei "$assign" | grep -Ev "$assign_exempt" \
|
||||
| while IFS= read -r line; do
|
||||
# The judged value is the assigned token alone: it ends at whitespace or `#`, so a
|
||||
# trailing comment cannot vouch for it. Truncating a quoted value early only ever errs
|
||||
# toward blocking, which is the safe direction for a gate that can be overridden.
|
||||
value=${line#*[:=]}
|
||||
value=${value#"${value%%[![:space:]]*}"}
|
||||
value=${value%%[[:space:]]*}
|
||||
value=${value%%#*}
|
||||
printf '%s\n' "$value" | grep -Eiq "$placeholders" && continue
|
||||
printf '%s\n' "$line"
|
||||
done)
|
||||
hits=$(printf '%s\n%s\n' "$hits" "$assigns" | sed '/^[[:space:]]*$/d')
|
||||
fi
|
||||
if [ -n "$hits" ]; then
|
||||
n=$(printf '%s\n' "$hits" | wc -l)
|
||||
count=$((count + n))
|
||||
report+=" $f"$'\n'"$(printf '%s\n' "$hits" | cut -c1-100 | sed 's/^/ /')"
|
||||
report+=" $f"$'\n'"$(printf '%s\n' "$hits" | cut -c1-100 | sed 's/^/ /')"$'\n'
|
||||
fi
|
||||
done <<< "$files"
|
||||
done < "$TMPD/files"
|
||||
|
||||
# Say so out loud: an exemption that prints nothing is how a carve-out becomes
|
||||
# invisible, and invisible is the property a secret wants.
|
||||
|
||||
+16
-4
@@ -86,15 +86,27 @@ agent/memories/--home-blank-.omp-wt-*
|
||||
# 2026-10-20), so this path in the repo was a duplicate of it, not its source — and
|
||||
# note that untracking deletes the copy in every other checkout regardless of
|
||||
# `git rm --cached` on the branch. A fresh box starts with an empty library; every
|
||||
# filename carries its wallhaven ID, so `curl -L -o <name> https://wallhaven.cc/w/<id>`
|
||||
# puts it back. Law: omp-docs/tools/dotfiles-deploy.md
|
||||
# filename carries its wallhaven ID; `/w/<id>` answers `text/html`, so resolve the CDN URL at
|
||||
# `https://wallhaven.cc/api/v1/w/<id>` and download `data.path` — the recipe, with the reason it
|
||||
# is not a plain `curl -L`, is on omp-docs/tools/dotfiles-deploy.md
|
||||
dotfiles/noctalia/.local/share/noctalia/wallpapers/
|
||||
|
||||
# --- generated theme output: machine-written, never reviewable, never deployed ---
|
||||
# noctalia/matugen rewrite these in place; a tracked copy is a stale snapshot that
|
||||
# deploy.sh would push back over a live palette. Patterns match the exclusion list in
|
||||
# scripts/deploy.sh. Law: omp-docs/inventory/home-config-census.md, Refs #42
|
||||
# deploy.sh would push back over a live palette. These patterns are the ignore half of the
|
||||
# exclusion list in `deploy_file` (scripts/deploy.sh): change one, change the other, or a
|
||||
# generated file becomes tracked-but-never-deployed — the fiction class #41 removed. The
|
||||
# capital-N rule is noctalia's own palette-directory naming, and it was in deploy.sh only.
|
||||
# Law: omp-docs/inventory/home-config-census.md, Refs #42, #60
|
||||
dotfiles/**/themes/noctalia*
|
||||
dotfiles/**/colors/noctalia*
|
||||
dotfiles/**/noctalia.*
|
||||
dotfiles/**/noctalia-*
|
||||
dotfiles/**/Noctalia*
|
||||
|
||||
# --- harness natives: unpacked runtime payloads, never configuration ----------
|
||||
# 2.4 MB of tree-sitter grammars the harness unpacks into the repo root on first use (measured
|
||||
# on blankpad 2026-10-21). Regenerable, unreviewable, not config — every clause of AGENTS.md's
|
||||
# runtime-state doctrine applies, and it sat unnamed in the root, one `git add -A` away from
|
||||
# being committed as if it were configuration.
|
||||
natives/
|
||||
|
||||
@@ -94,19 +94,16 @@ knowledge base. Those pages own their facts; this is only the pointer.
|
||||
|
||||
### Issue tracker
|
||||
|
||||
Work items are Gitea issues on `dank/command-center` (Dankclaw,
|
||||
`192.168.0.143:3000`) — no `gh`, no `tea`. See
|
||||
[`omp-docs/skills/issue-tracker.md`](omp-docs/skills/issue-tracker.md).
|
||||
See [`omp-docs/skills/issue-tracker.md`](omp-docs/skills/issue-tracker.md).
|
||||
|
||||
### Triage labels
|
||||
|
||||
The five canonical roles, each label string equal to its name. See
|
||||
[`omp-docs/skills/triage-labels.md`](omp-docs/skills/triage-labels.md).
|
||||
See [`omp-docs/skills/triage-labels.md`](omp-docs/skills/triage-labels.md).
|
||||
|
||||
### Domain docs
|
||||
|
||||
Single-context, inside the existing knowledge base: `omp-docs/glossary.md` and
|
||||
`omp-docs/decisions/`. See [`omp-docs/skills/domain-docs.md`](omp-docs/skills/domain-docs.md).
|
||||
ADRs: `omp-docs/decisions/`. `omp-docs/glossary.md` is intentionally not created.
|
||||
See [`omp-docs/skills/domain-docs.md`](omp-docs/skills/domain-docs.md).
|
||||
|
||||
## Secret gate (HARD rule)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ Documentation exists for ONE reason: **point of use**. A page earns its bytes on
|
||||
#### 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.
|
||||
- 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. In `dank/command-center` the KB linter reports any `log.md` that is not the bundle root's; the rule and its reasoning are owned by `omp-docs/references/okf-spec.md`.
|
||||
- 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.
|
||||
|
||||
|
||||
+68
-17
@@ -16,7 +16,7 @@ providers:
|
||||
auth: none
|
||||
models:
|
||||
- id: frank-archon
|
||||
# Model id renamed from frank-archon on 2026-10-08 (official
|
||||
# Model id renamed from frank-medium on 2026-10-08 (official
|
||||
# unit-name scheme; the systemd service, scripts and runbooks keep
|
||||
# the frank-archon name — service names are unchanged).
|
||||
# Server-side compaction routes to compaction.remoteEndpoint in
|
||||
@@ -82,9 +82,11 @@ providers:
|
||||
auth: none
|
||||
models:
|
||||
- id: frank-dragoon
|
||||
# Model id renamed from frank-dragoon on 2026-10-08 (official
|
||||
# Model id renamed from frank-large on 2026-10-08 (official
|
||||
# unit-name scheme; the service/unit/scripts keep the frank-dragoon
|
||||
# name — service names are unchanged).
|
||||
# name — service names are unchanged). The pre-rename id is
|
||||
# `frank-large`, recorded here because the callsign rename overwrote
|
||||
# its own explanation and left "renamed from frank-dragoon".
|
||||
name: "frank-dragoon (dankclaw-gpu)"
|
||||
# Server-side compaction runs on the compaction.remoteEndpoint in
|
||||
# config.yml (192.168.0.143:7771 — frank-zealot's 3090); this steers the
|
||||
@@ -111,31 +113,80 @@ providers:
|
||||
maxTokens: 45056
|
||||
reasoning: true
|
||||
# VISION ON as of 2026-10-06: the v2 vision tower loads beside the
|
||||
# checkpoint (HALOGEN_VISION_TOWER; red-PNG probe verified on the
|
||||
# card). image_url content parts only — the legacy top-level
|
||||
# images field is the frank-zealot trap, do not repeat it here.
|
||||
# checkpoint (HALOGEN_VISION_TOWER; priced at 0.8 GiB in the boot
|
||||
# ledger, and re-probed 2026-10-21 — a 512 px solid-red PNG comes back
|
||||
# "Red" at 282 prompt tokens). image_url content parts only — the
|
||||
# legacy top-level images field is the frank-zealot trap, do not
|
||||
# repeat it here.
|
||||
input: [text, image]
|
||||
compat:
|
||||
# flash-server emits thinking as `reasoning_content` — same
|
||||
# field as every engine this endpoint has run, no harness
|
||||
# change needed.
|
||||
reasoningContentField: "reasoning_content"
|
||||
# Same Qwen3.8 template family (it ships inside the tokenizer
|
||||
# bundle): any reasoning_effort outside {xhigh, medium, low} is
|
||||
# rejected, not clamped — the fold stays mandatory.
|
||||
# flash-server validates reasoning_effort against its own list and
|
||||
# 400s anything else, naming it: "reasoning_effort 'maximum'
|
||||
# unsupported; use minimal|low|medium|high|xhigh|max|none" (measured
|
||||
# 2026-10-21). Every omp level is already spelled by the engine, so
|
||||
# the map is identity — it exists to record that, not to fold. An
|
||||
# earlier version rewrote high->xhigh and minimal->low to stop
|
||||
# rejections that do not exist, and the rewrite asked the engine for
|
||||
# a different thinking budget than the caller named. Levels really do
|
||||
# differ here: thinking length on one prompt at max_tokens 2048
|
||||
# measured low 766 tokens, xhigh 614, max 1049 with the block closed
|
||||
# at 1024 (reasoning_closed_at). The harness passes an unmapped level
|
||||
# through verbatim (`compat.reasoningEffortMap?.[effort] ?? effort`),
|
||||
# so removing an entry is a pass-through, never a 400.
|
||||
#
|
||||
# `max` still does not reach the engine, and the map is not why: the
|
||||
# harness clamps the requested effort to the model's own effort ladder
|
||||
# before the map applies, and its ladder for this model tops out at
|
||||
# xhigh — captured on the wire 2026-10-21, `:max` left as
|
||||
# `reasoning_effort: "xhigh"` with this identity map in place. The
|
||||
# `max` row below is the engine-side half of the contract; it takes
|
||||
# effect the moment the harness ladder gains `max`, and nothing here
|
||||
# can force it sooner.
|
||||
#
|
||||
# Thinking OFF needs an explicit mode. The default disable mode sends
|
||||
# the LOWEST effort rather than a switch — `:off` left the wire as
|
||||
# `reasoning_effort: "minimal"` (captured), and minimal still thinks:
|
||||
# a canned 26-token block on every "off" request. `none-effort` makes
|
||||
# the harness send `reasoning_effort: "none"`, which this engine
|
||||
# honours as a real switch: `reasoning_tokens: 0`, answer only, no
|
||||
# thinking block in the rendered output (both verified 2026-10-21).
|
||||
#
|
||||
# Re-measure before changing any of it. The allow-list, in the
|
||||
# engine's own words:
|
||||
# curl -s :7770/v1/chat/completions -H 'content-type: application/json' \
|
||||
# -d '{"model":"frank-dragoon","messages":[{"role":"user","content":"x"}],
|
||||
# "reasoning_effort":"maximum"}'
|
||||
# -> 400 "reasoning_effort 'maximum' unsupported; use
|
||||
# minimal|low|medium|high|xhigh|max|none"
|
||||
# and read usage.completion_tokens_details.reasoning_tokens at
|
||||
# max_tokens 2048 to see the levels separate. The wire itself is
|
||||
# settled by pointing a scratch HOME at a logging proxy on :7799 that
|
||||
# forwards to :7770 and running `omp --model …:off` — config claims
|
||||
# are worth exactly what their last capture says.
|
||||
reasoningDisableMode: none-effort
|
||||
reasoningEffortMap:
|
||||
minimal: low
|
||||
minimal: minimal
|
||||
low: low
|
||||
medium: medium
|
||||
high: xhigh
|
||||
max: xhigh
|
||||
# The card's thinking recipe, on the wire. The flash-server
|
||||
# engine HAS a server-side sampler (HALOGEN_TEMPERATURE/TOP_P/
|
||||
# TOP_K = 1.0 / 0.95 / 20 in frank-dragoon.env) — these match it,
|
||||
# so per-request values are consistent, not load-bearing.
|
||||
high: high
|
||||
xhigh: xhigh
|
||||
max: max
|
||||
# The card's thinking recipe, on the wire. The flash-server engine
|
||||
# HAS a server-side sampler (HALOGEN_TEMPERATURE/TOP_P/TOP_K =
|
||||
# 1.0 / 0.95 / 20 in frank-dragoon.env), but an omitted parameter is
|
||||
# the engine's choice, not a pinned one: top_k is sent explicitly so
|
||||
# the recipe survives someone editing HALOGEN_TOP_K. The engine
|
||||
# accepts top_k, min_p, presence_penalty and repetition_penalty
|
||||
# (measured 2026-10-21, all 200) — only the values this endpoint is
|
||||
# meant to run are pinned here.
|
||||
extraBody:
|
||||
temperature: 1.0
|
||||
top_p: 0.95
|
||||
top_k: 20
|
||||
|
||||
"llamacpp (dankclaw-3090)":
|
||||
baseUrl: http://192.168.0.143:7771/v1
|
||||
@@ -143,7 +194,7 @@ providers:
|
||||
auth: none
|
||||
models:
|
||||
- id: frank-zealot
|
||||
# Model id renamed from frank-zealot on 2026-10-08 (official
|
||||
# Model id renamed from frank-small on 2026-10-08 (official
|
||||
# unit-name scheme; the service/unit/scripts keep the
|
||||
# frank-zealot name — service names are unchanged).
|
||||
name: "frank-zealot (dankclaw-3090)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Unit]
|
||||
Description=swayidle idle lock (locks via noctalia-shell IPC, suspends on lid)
|
||||
Description=swayidle idle lock watcher (arms the idle lock via noctalia-shell IPC; suspend is covered by noctalia's sleep inhibitor)
|
||||
Documentation=https://github.com/swaywm/swayidle
|
||||
After=graphical-session.target
|
||||
# Restart together with the session, not merely at boot: this unit is wanted by
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
// requires: Lenovo Group Limited 0x40A9 Unknown
|
||||
// optional: HDMI-A-1 (PM1601, dock-dependent)
|
||||
|
||||
// This panel's EDID tops out at 3440x1440@180 (read live from niri: no 240 Hz mode
|
||||
// exists). Ask for a mode the panel does not offer and niri does not fail — it logs
|
||||
// "could not be found, falling back to preferred" and drops to 60 Hz on every
|
||||
// reconnect. Keep 180 here unless a re-read EDID says otherwise.
|
||||
// The panel offers 3440x1440@239.998 as well as @180.000 (`niri msg outputs`, 2026-10-21), and
|
||||
// 180 is driven DELIBERATELY: the link trained at x8 32.0 GT/s against a max of x16 and the
|
||||
// display engine is throwing DMCUB errors with EINVAL page flips after s2idle, so the 240-class
|
||||
// mode stays undriven until a reboot clears that state. niri matches mode strings VERBATIM and
|
||||
// does not round: "@239.999" is not "@239.998", and a mode it cannot find does not fail — it
|
||||
// logs "could not be found, falling back to preferred" and drops the panel to 60 Hz on every
|
||||
// reconnect, which is how this desk ran for weeks. Copy the string from `niri msg outputs`.
|
||||
// Reasoning and triage: omp-docs/tools/niri/displays.md ("The config pattern", rules 4 and 5).
|
||||
output "Sceptre Tech Inc Sceptre M34 Unknown" {
|
||||
mode "3440x1440@180.000"
|
||||
scale 1
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -1,8 +1,8 @@
|
||||
# Architecture decision records
|
||||
|
||||
Decisions this repo has made and will not silently revisit. One file per
|
||||
decision, named `NNN-<slug>.md`, listed here — an unlisted page fails `kb-lint`
|
||||
and does not exist as far as any agent is concerned.
|
||||
decision, named `NNN-<slug>.md`, listed here — an unlisted page fails
|
||||
`scripts/kb-coverage.py` and does not exist as far as any agent is concerned.
|
||||
|
||||
**No ADRs yet.** The first one lands the moment a decision stops being
|
||||
reversible.
|
||||
|
||||
+2
-2
@@ -57,8 +57,8 @@ search practices, open models, MCP.
|
||||
|
||||
## [Inventory](inventory/index.md)
|
||||
|
||||
Pre-pruning census of every userspace config on this box — the input for the
|
||||
pruning session.
|
||||
Per-file tracked-vs-box-only census of `~/.config` on blankpad — the input for the
|
||||
dotfiles pruning/adoption session.
|
||||
|
||||
## [Decisions](decisions/index.md)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Inventory Index
|
||||
|
||||
Census of all userspace program configuration on the workstation.
|
||||
Tracked-vs-box-only census of userspace configuration on blankpad. Read the scope before
|
||||
trusting coverage: these pages measure `~/.config`, not `~/.local/share`, not system package
|
||||
state, not the servers.
|
||||
|
||||
- [home-config-census.md](home-config-census.md) — Complete inventory of top-level dotfiles, ~/.config, ~/.local/share state, and special directories (~/.pi, ~/.omp)
|
||||
- [home-config-census.md](home-config-census.md) — Per-file tracked-vs-box-only census of ~/.config on blankpad — the input for the dotfiles pruning/adoption session.
|
||||
|
||||
+110
-1
@@ -6,7 +6,7 @@ tags:
|
||||
- okf
|
||||
- maintenance
|
||||
- changelog
|
||||
timestamp: 2026-10-19
|
||||
timestamp: 2026-10-21
|
||||
---
|
||||
|
||||
# Bundle Change Log
|
||||
@@ -16,6 +16,115 @@ 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 is the bundle's only work log; [references/okf-spec.md](references/okf-spec.md) owns the
|
||||
one-log rule and `scripts/kb-lint.sh` enforces it.
|
||||
|
||||
## 2026-10-21 — reviewing a day of work, and finding checks that lie
|
||||
|
||||
Two-axis review (Standards / Spec) of everything landed 2026-10-20 → 2026-10-21: 51 commits,
|
||||
30 findings, worked on `fix/60-review-findings` (issue #60). Three findings were the same shape —
|
||||
a check that reports confidence it did not earn:
|
||||
|
||||
- **A guard that cannot see must refuse.** `pcie-probe.sh` dropped the `/dev/kfd` half of its
|
||||
busy check when `fuser` was absent, and `gpu_busy_percent` reads `0` for a resident-but-idle
|
||||
service: the tool measured beside a live endpoint while its own LAW said not to. Missing
|
||||
visibility is now a refusal (exit `3`), not a narrower check.
|
||||
- **A swallowed status is a false report.** `deploy.sh` printed `ENABLE` for units systemd
|
||||
refuses to enable (`live-sync.service` has no `[Install]` section; its timer arms it), and the
|
||||
loop walked `*.service` only, so a rebuilt box deployed the heartbeat and never armed its
|
||||
timer. Every outcome now reports what actually happened.
|
||||
- **A gate that reads a quoted path reads nothing.** git quotes non-ASCII paths in
|
||||
`--name-only` output; the quoted name came back as an empty diff and the secret gate
|
||||
`continue`d past it. Fail closed means `-z`, and it means the placeholder filter reads the
|
||||
assigned value — a comment saying `# example` cannot vouch for the token beside it.
|
||||
|
||||
The law that cost the most, from the probe: **a test that observes the tool's environment instead
|
||||
of what the tool passed on cannot see the bug.** The stub probe echoed `$HIP_VISIBLE_DEVICES`
|
||||
rather than its argument, so the suite asserted the mask was set and per-card bandwidth was
|
||||
unobtainable on the 2×R9700 box. Model the platform rule in the stub and the existing test goes
|
||||
red against the old code — that is the red-proof worth having.
|
||||
|
||||
Same shape in `remote-job.sh status`: it globbed its arguments, so a job named `x*` was reported
|
||||
as whatever happened to match in the caller's directory. A name is data — split it (`read -a`),
|
||||
never let the shell expand it — and a multiplexed `tail` with no jobs says "no jobs" instead of
|
||||
handing `*.out` to tail and reporting a missing file.
|
||||
|
||||
Incident, found while verifying the gate: the live-sync heartbeat had failed three hourly ticks,
|
||||
because a managed skill that documents how to write a secret gate quotes the vendor's all-zeros
|
||||
placeholder id and the gate's shape rule fired on the documentation. Prose about credentials is
|
||||
what the machine tier is made of; a gate that blocks it is worked around, and the workaround is
|
||||
the hole. Placeholder judgement reads the matched token — plus what follows it, because a fixed
|
||||
quantifier truncates `EXAMPLE` to `EXA` — never the whole line. `ALLOW_SECRETS` stays the only
|
||||
override, and the heartbeat's own payload was replayed through the fixed hook to prove it lands.
|
||||
|
||||
The rest of #60 was claims about the machine that the machine had stopped honouring:
|
||||
`.gitignore` and `deploy.sh`'s exclusion list had drifted apart (`Noctalia*` was excluded, never
|
||||
ignored), `.gitattributes` was the stated reason `git-lfs` was installed — for wallpapers that
|
||||
same week's diff untracked, and the one-log rule was restated in four owners. A coverage claim
|
||||
names what was measured; a tool justified by dead config is a tool nobody can defend when the
|
||||
config goes.
|
||||
|
||||
## 2026-10-21 — an effort map folding against rejections that never existed
|
||||
|
||||
`agent/models.yml` rewrote `minimal→low`, `high→xhigh` and `max→xhigh` for
|
||||
frank-dragoon to dodge 400s the engine does not send: flash-server's own error names
|
||||
its allow-list (`minimal|low|medium|high|xhigh|max|none`) and the harness passes an
|
||||
unmapped level through verbatim, so the fold only asked for a different thinking
|
||||
budget than the caller named. The map is identity now, and
|
||||
`reasoningDisableMode: none-effort` was added because the default disable mode does
|
||||
not disable — `:off` left as `reasoning_effort: "minimal"` and paid for a canned
|
||||
thinking block, where `none` returns `reasoning_tokens: 0`. Both settled by capturing
|
||||
the wire through a logging proxy rather than reading the config: a claim about what
|
||||
the harness sends is worth exactly its last capture. The same pass restored the three
|
||||
pre-callsign model ids (`frank-medium` / `frank-large` / `frank-small`) that the
|
||||
2026-10-08 rename had overwritten inside its own explanatory comments. Refs #62
|
||||
|
||||
## 2026-10-21 — a documented mode the panel never had
|
||||
|
||||
The home ultrawide had been running at 60 Hz for weeks while `desk-1-home.kdl`
|
||||
asked for `3440x1440@239.999`. The page that owns that file documented the same
|
||||
phantom mode, so every reader — including tonight's inspection — trusted it.
|
||||
The panel does offer a 240-class mode: `niri msg outputs` lists `3440x1440@239.998`. The defect
|
||||
was one character — niri matches mode strings verbatim, and `239.999` is not `239.998`.
|
||||
|
||||
Laws, now on [tools/niri/displays](tools/niri/displays.md):
|
||||
|
||||
- **A mode string must match the advertised string verbatim.** niri does not round:
|
||||
`3440x1440@239.999` does not select `3440x1440@239.998`. It logs
|
||||
`configured mode … could not be found, falling back to preferred` and drives the EDID
|
||||
preferred mode — 60 Hz here. A one-character typo therefore looks like a dying monitor, and
|
||||
every connector drop re-enacts it.
|
||||
- **Three layers, three signatures:** config fallback, DP link drops, and
|
||||
amdgpu `DMCUB` errors with `EINVAL` page flips after s2idle. Only the first
|
||||
yields to a file edit; only the last yields to a reboot. Triage table and
|
||||
probes are on the page.
|
||||
- **A number in a page is a claim, not a measurement.** Mode strings, refresh rates and tool
|
||||
availability were wrong on several pages at once — `wlr-randr` was listed as installed while
|
||||
`pacman -Qs wlr` matches nothing. Read the hardware before writing a mode into a runbook, and
|
||||
destroy the stale copies. A correction is itself a measurement: the first pass at this entry
|
||||
published a second wrong hardware fact, with a `verified:` date stamped on it.
|
||||
|
||||
Fix: `desk-1-home.kdl` 239.999 → 180.000 on the identity block and both legacy
|
||||
connector blocks, applied live with `niri msg output <panel> mode <mode>` — the
|
||||
temporary per-output path, so no config reload and no logged-out session.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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.
|
||||
Exactly one `log.md` per bundle, at the bundle root — [okf-spec.md](okf-spec.md) owns that rule
|
||||
and `scripts/kb-lint.sh` enforces it, reporting any `log.md` that is not the root's. The
|
||||
maintenance action is collapse, never coexistence: when a second log turns up (`core/log.md`,
|
||||
`notes.md`, a per-run diary), move its entries into the root log under their own dates, delete
|
||||
the file, and re-run the linter.
|
||||
|
||||
**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 paths in links — the RECOMMENDED form is an absolute bundle path
|
||||
(`/tools/index.md`), resolved against the bundle root; the mistake is a path that
|
||||
names no file there, and that is what the linter reports.
|
||||
- ❌ 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ Applications that ship with or are configured on CachyOS with Niri on the workst
|
||||
| **Noctalia Shell** | 4.7.7 | Desktop shell (launcher, wallpaper, session menu) |
|
||||
| **Shelly Notifications** | — | Notification daemon |
|
||||
| **pavucontrol** | — | Audio mixer |
|
||||
| **wlr-randr** | — | Display management |
|
||||
|
||||
## Screenshot and Recording
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ CachyOS with Niri ships with a curated set of applications:
|
||||
- **wf-recorder** — Screen recording
|
||||
- **hyprpicker** — Color picker
|
||||
- **wl-clipboard** — Clipboard management
|
||||
- **wlr-randr** — Display management
|
||||
- **NetworkManager** — Network management
|
||||
- **BlueZ** — Bluetooth
|
||||
- **pavucontrol** — Audio mixer
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
type: reference
|
||||
title: Dotfiles deployment tier
|
||||
description: How dotfiles/ packages become live configuration — stow-equivalent symlinks, host override packages, and the two laws about theme media and systemd enable symlinks.
|
||||
description: How dotfiles/ packages become live configuration — deploy.sh modes, host override packages, and the tier's hard-won laws, from media and generated theme output to systemd enable/start semantics, dconf dumps, and idle-lock watcher triage.
|
||||
tags:
|
||||
- dotfiles
|
||||
- deployment
|
||||
- systemd
|
||||
- noctalia
|
||||
timestamp: 2026-10-20
|
||||
verified: 2026-10-20
|
||||
verified: 2026-10-21
|
||||
---
|
||||
|
||||
# Dotfiles deployment tier
|
||||
@@ -25,10 +25,15 @@ is stowed last and its files replace symlinks coming from base packages.
|
||||
| `deploy.sh` (sync) | adopt identicals, link new, quarantine divergents into `.quarantine/` and replace them |
|
||||
| `deploy.sh --adopt` | like sync, but divergents are reported and skipped rather than quarantined |
|
||||
|
||||
Two package shapes are not symlinks. `dconf/dump.txt` is loaded with `dconf load /`, and
|
||||
`systemd-user` units get `daemon-reload` plus `enable`; a unit is **started** only when
|
||||
systemd reports it as nothing-yet (`""`/`disabled`), and never when its `ExecStart` binary
|
||||
is absent here, so a unit belonging to another box stays inert instead of crash-looping.
|
||||
Two package shapes are not symlinks. `dconf/dump.txt` is loaded with `dconf load /`. The
|
||||
`systemd-user` tier gets `daemon-reload`, then **every `*.service` and `*.timer` whose start
|
||||
command resolves on this box** is enabled — a timer is the schedule that makes a oneshot service
|
||||
run at all, and it carries no `ExecStart` of its own, so the belonging test is applied to the
|
||||
service it activates. A unit is **started** (`--now`) only when systemd reports it as nothing-yet
|
||||
(`""`/`disabled`), and never when its binary is absent here, so a unit belonging to another box
|
||||
stays inert instead of crash-looping. Three outcomes are printed, and each says what actually
|
||||
happened: `ENABLE` only when `systemctl enable` returned success, `INERT` when it could not or did
|
||||
not, `SKIP` when the unit is foreign. Refs #44, #60.
|
||||
Starting something already installed is what makes `deploy.sh` unsafe to run beside a live
|
||||
session — see *Never start a unit a session may already be running*.
|
||||
|
||||
@@ -54,8 +59,17 @@ checkout, `git rm --cached` on the branch notwithstanding.
|
||||
|
||||
Restore on a fresh box: the library lives in `~/.local/share/noctalia/wallpapers/`, a path
|
||||
`deploy.sh` does not manage. Every filename is `wallhaven_<id>.<ext>` and the ID is the
|
||||
whole address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` into that directory puts
|
||||
the set back. Losing it is an acceptable outcome; 80 MB per clone forever is not.
|
||||
whole address — but `https://wallhaven.cc/w/<id>` answers `text/html`, the web page, so
|
||||
resolve the direct CDN URL through the API and download that:
|
||||
|
||||
```bash
|
||||
id=73g8xo # the wallhaven_<id> stem of any file in the set
|
||||
url=$(curl -s "https://wallhaven.cc/api/v1/w/$id" | grep -o '"path":"[^"]*' | cut -d'"' -f4)
|
||||
curl -L -o ~/.local/share/noctalia/wallpapers/"${url##*/}" "$url"
|
||||
```
|
||||
|
||||
`data.path` already ends in `wallhaven_<id>.<ext>`, so the filename keeps itself. Losing it
|
||||
is an acceptable outcome; 80 MB per clone forever is not.
|
||||
|
||||
## Law: never `systemctl --user enable` a unit from a worktree
|
||||
|
||||
@@ -158,6 +172,15 @@ 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.
|
||||
|
||||
**A third state exists, and it is not an error.** A unit file with no `[Install]` section cannot be
|
||||
enabled at all — `systemctl --user enable` answers with a non-zero exit and a paragraph about
|
||||
missing `WantedBy`. `live-sync.service` is one on purpose: `live-sync.timer` pulls it in, which is
|
||||
also why systemd reports it as `linked`. Until 2026-10-21 the deploy swallowed that failure and
|
||||
printed `ENABLE` anyway, so the line an operator reads to learn what a rebuild armed was false on
|
||||
every box, on the repo's own heartbeat. The section is readable from the file being deployed, so
|
||||
the tool checks it and prints `INERT …(no [Install] section — another unit arms it, enable cannot)`
|
||||
without making the call. Refs #60.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -9,7 +9,7 @@ Configuration guides and reference material for installed software. Each tool ha
|
||||
* [applications](applications.md) — Default userspace applications on CachyOS with Niri — browsers, editor, terminal, file manager, audio, screenshots, gaming.
|
||||
* [cachyos](cachyos.md) — Arch-based rolling release with performance-optimized kernels, repositories, and gaming stack. Running on the workstation.
|
||||
* [comfyui](comfyui/index.md) — Node-based AI image generation engine on dankclaw. FLUX.2 klein 4B, CPU backend, systemd managed.
|
||||
* [dotfiles-deploy](dotfiles-deploy.md) — How `dotfiles/` packages become live config: `deploy.sh` modes, host override packages, and why theme media and worktree-enabled units are traps.
|
||||
* [dotfiles-deploy](dotfiles-deploy.md) — How `dotfiles/` packages become live config: `deploy.sh` modes, host override packages, and the tier's hard-won laws, from media and generated theme output to systemd enable/start semantics, dconf dumps, and idle-lock watcher triage.
|
||||
* [ghostty](ghostty/index.md) — GPU-accelerated terminal emulator with extensive configuration, keybindings, and shaders.
|
||||
* [gitea](gitea/index.md) — Private Git hosting on dankclaw. Quadlet-managed containers: Gitea, PostgreSQL, Caddy.
|
||||
* [handy](handy.md) — Offline speech-to-text transcription with WebKitGTK backend and Tauri v2 UI.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
type: reference
|
||||
title: Display Configuration
|
||||
description: Niri output configuration on blankpad — EDID-identity-keyed desk profiles, hotplug auto-switching, portrait transforms, position pinning, and GPU notes (AMD Cezanne now; Intel Arc render-node pin is dankware-legacy).
|
||||
description: Niri output configuration on blankpad — EDID-identity-keyed desk profiles, hotplug auto-switching, portrait transforms, position pinning, the phantom-mode fallback trap, and GPU notes (AMD Cezanne now; Intel Arc render-node pin is dankware-legacy).
|
||||
tags:
|
||||
- niri
|
||||
- display
|
||||
- monitors
|
||||
- rotation
|
||||
- desk-profiles
|
||||
verified: 2026-10-09
|
||||
verified: 2026-10-21
|
||||
---
|
||||
|
||||
# Display Configuration
|
||||
@@ -24,7 +24,11 @@ active profile (`desk-1-home.kdl`, `desk-2-portable.kdl`, …).
|
||||
`config.kdl` includes it with `include optional=true` so a missing link is a
|
||||
warning, not a parse failure.
|
||||
**Probe:** `niri msg outputs` is authoritative (connector, full EDID long
|
||||
name, modes, transform, scale, position, VRR). `wlr-randr` remains available.
|
||||
name, modes, transform, scale, position, VRR). Per-output changes go live with
|
||||
`niri msg output <name> <mode|scale|transform|position|off|on>` and are
|
||||
temporary — never written to the config file. `wlr-randr` is **not installed**
|
||||
on blankpad (`pacman -Qs wlr` matches nothing, verified 2026-10-21); pages that
|
||||
list it as an installed tool are stale.
|
||||
|
||||
---
|
||||
|
||||
@@ -33,21 +37,23 @@ name, modes, transform, scale, position, VRR). `wlr-randr` remains available.
|
||||
```
|
||||
┌──────────────┬──────────────────────────────┬──────────┐
|
||||
│ PM1601 │ Sceptre M34 ultrawide │ eDP-1 │
|
||||
│ portrait │ 3440x1440 @ 239.999 │ 1920x1080│
|
||||
│ portrait │ 3440x1440 @ 180.000 │ 1920x1080│
|
||||
│ 1200x1920 │ landscape, scale 1 │ scale 1 │
|
||||
│ rot 90 │ position 1581,-600 │ 5021,-240│
|
||||
└──────────────┴──────────────────────────────┴──────────┘
|
||||
HDMI-A-1 DP-2 (focus-at-startup) eDP-1 (fallback focus)
|
||||
HDMI-A-1 DP-1 (focus-at-startup) eDP-1 (fallback focus)
|
||||
Connector names are today's (`niri msg outputs`, 2026-10-21). They reshuffle with
|
||||
the dock and the cable, which is why the config keys on the EDID long name instead.
|
||||
```
|
||||
|
||||
| Panel | EDID long name | Keyed by | Notes |
|
||||
|-------|----------------|----------|-------|
|
||||
| Sceptre M34 ultrawide | `Sceptre Tech Inc Sceptre M34 Unknown` | identity | `mode "3440x1440@239.999"`, `focus-at-startup` |
|
||||
| Sceptre M34 ultrawide | `Sceptre Tech Inc Sceptre M34 Unknown` | identity | `mode "3440x1440@180.000"`, **deliberate** — the panel also offers `3440x1440@239.998`, `144.001` and `120.000`; 180 is driven while the link is degraded (see the law below) — `focus-at-startup` |
|
||||
| PM1601 portrait | *not captured* | connector `HDMI-A-1` | dock-dependent; run `desk.sh capture` when plugged in, then re-key |
|
||||
| eDP-1 laptop panel | `Lenovo Group Limited 0x40A9 Unknown` | identity | scale **1.0** (deliberate; Ghostty grid at 1.25 rejected 2026-10-09), second `focus-at-startup` |
|
||||
|
||||
VRR: DP-2 supports it; **left disabled by decision** (2026-10-09). Add
|
||||
`variable-refresh-rate` per output if that ever changes.
|
||||
VRR: the M34 supports it (its connector today is `DP-1`); **left disabled by
|
||||
decision** (2026-10-09). Add `variable-refresh-rate` per output if that ever changes.
|
||||
|
||||
`focus-at-startup` on both M34 and eDP-1: niri focuses in config order, so
|
||||
the ultrawide wins when present and the laptop panel is the fallback.
|
||||
@@ -84,7 +90,7 @@ graph LR
|
||||
```kdl
|
||||
// desk-1-home.kdl — one block per panel, keyed by identity
|
||||
output "Sceptre Tech Inc Sceptre M34 Unknown" {
|
||||
mode "3440x1440@239.999"
|
||||
mode "3440x1440@180.000"
|
||||
scale 1
|
||||
transform "normal"
|
||||
position x=1581 y=-600
|
||||
@@ -98,6 +104,10 @@ Four rules learned the hard way:
|
||||
name *and* long name) fight over properties — transform reverts are the
|
||||
classic symptom. Desk profiles are self-contained: a panel's identity,
|
||||
mode, transform, **and** position live in exactly one block per file.
|
||||
`desk-1-home.kdl` still carries legacy connector-keyed `DP-1` / `DP-2`
|
||||
blocks next to the identity block. Their modes were aligned to 180 Hz on
|
||||
2026-10-21 so a connector-named reconnect cannot drop the panel to 120, but
|
||||
they belong folded into the identity block.
|
||||
2. **Key by EDID identity, not connector.** Connector names swap between
|
||||
docks; the EDID long name travels with the panel. Serials on these panels
|
||||
read `Unknown`, so the full long name (manufacturer model serial) is the
|
||||
@@ -105,8 +115,57 @@ Four rules learned the hard way:
|
||||
3. **Positions are absolute, not relational.** A disconnected panel leaves
|
||||
a hole; that is exactly why positions belong to the *desk profile*, not a
|
||||
global file.
|
||||
4. **Modes must match what the panel reports** (`niri msg outputs` →
|
||||
`Mode: current …`). A mode the panel cannot drive leaves the output dark.
|
||||
4. **Ask only for modes the panel reports, and copy the string exactly.** The M34's
|
||||
`Available modes` (`niri msg outputs`, 2026-10-21) are `3440x1440@239.998`,
|
||||
`3440x1440@180.000`, `144.001`, `120.000`, … with `59.998` marked preferred. niri
|
||||
matches mode strings **verbatim** and does not round, so `239.999` — one digit from
|
||||
the real mode, which is what this file and the config both carried for weeks — is not
|
||||
`239.998`. A mode it cannot find does **not** leave the output dark: niri logs
|
||||
`configured mode … could not be found, falling back to preferred` and quietly drives
|
||||
the panel at its EDID *preferred* mode — **60 Hz**. Nothing looks broken except the
|
||||
refresh rate, and every reconnect repeats the fallback. Check Hz, not just
|
||||
resolution, after any "monitor is acting up".
|
||||
5. **180 Hz here is a decision, not a ceiling.** The panel offers 239.998 and is driven
|
||||
at 180.000 deliberately: the link trained at `x8 32.0 GT/s` against a max of `x16`,
|
||||
and the display engine is throwing `DMCUB` errors with `EINVAL` page flips after
|
||||
s2idle, so the 240-class mode is not driven until a reboot clears that state. Before
|
||||
changing the mode, re-read `niri msg outputs` **and** the link sysfs; treat "restore
|
||||
240" as a change to verify on a healthy link, never as the obvious fix. Refs #60.
|
||||
|
||||
## "The monitors are broken" — three layers, three signatures
|
||||
|
||||
A display complaint belongs to one of three layers. Sort it before editing
|
||||
config, because the config is usually innocent — and when it is guilty, the
|
||||
guilt may have been copied from this page.
|
||||
|
||||
| Layer | Log signature | Reality | Move |
|
||||
|-------|---------------|---------|------|
|
||||
| Config | `configured mode … could not be found, falling back to preferred` | a mode/scale the panel never offered | re-read the mode list with `niri msg outputs` and correct the file; do **not** trust a documented mode |
|
||||
| Link | `disconnecting connector: "DP-1"` followed seconds later by `new connector: DP-1`, recurring | the DP link itself dropping — cable, dock, the monitor's DP-version/DSC setting, or panel power saving | every drop re-runs mode selection, so a config error is re-enacted in public; swap the cable before blaming the compositor |
|
||||
| Driver | `Page flip commit failed … (Invalid argument)` plus kernel `*ERROR* … DMCUB error` | amdgpu's display engine wedged, typically after an s2idle resume | survives until a reboot; niri keeps running while the panel keeps dropping. No config edit clears it |
|
||||
|
||||
Probes, in this order:
|
||||
|
||||
```bash
|
||||
niri msg outputs # connector, EDID long name, real mode list, current mode
|
||||
journalctl --user -b | grep -E 'disconnecting connector|Page flip commit failed|falling back to preferred'
|
||||
journalctl -k -b | grep -iE 'amdgpu.*(ERROR|DMCUB)|PM: suspend exit'
|
||||
```
|
||||
|
||||
Correlate the drop timestamps against `PM: suspend exit` first. A resume a
|
||||
minute before the first drop is the cause; the config file is not.
|
||||
|
||||
To fix a refresh rate without disturbing the session, use the temporary
|
||||
per-output path — it touches one output, applies no config, reloads nothing:
|
||||
|
||||
```bash
|
||||
niri msg output "Sceptre Tech Inc Sceptre M34 Unknown" mode "3440x1440@180.000"
|
||||
```
|
||||
|
||||
It is temporary by design, so correct the profile too and let the next
|
||||
reconnect or `load-config-file` make it permanent. With the display engine
|
||||
already throwing `EINVAL`, prefer the per-output path over a full config
|
||||
reload; on a healthy machine either is fine.
|
||||
|
||||
## GPU assignment
|
||||
|
||||
@@ -132,4 +191,4 @@ symlink matrix]]).
|
||||
- [Niri](/tools/niri/index.md) — compositor overview and config layout
|
||||
- [[tips|tips]] — reload path, `niri msg layers/casts`, includes
|
||||
- [[noctalia-shell|noctalia-shell]] — notification shown on desk switch
|
||||
- [[cachyos|cachyos]] — operating system, including `wlr-randr`
|
||||
- [[cachyos|cachyos]] — operating system
|
||||
|
||||
@@ -413,17 +413,21 @@ noctalia msg templates-apply
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| `noctalia` (Qt) | Native desktop environment |
|
||||
| `qt6-base` | Qt6 runtime |
|
||||
| `qt6-multimedia` | Multimedia support |
|
||||
| `niri` | Wayland compositor |
|
||||
| `wlr-randr` | Display information |
|
||||
| `brightnessctl` | Brightness control |
|
||||
| `imagemagick` | Image processing |
|
||||
| `ffmpeg` | Media processing |
|
||||
| `polkit` | PolicyKit integration |
|
||||
| Package | Purpose | Version on blankpad (2026-10-21) |
|
||||
|---------|---------|----------------------------------|
|
||||
| `noctalia` (Qt) | the shell: bar, widgets, `noctalia msg` | 5.3.0-1.1 |
|
||||
| `qt6-base` | Qt6 runtime | 6.12.0-3.1 |
|
||||
| `niri` | Wayland compositor | 26.04-1.1 |
|
||||
| `polkit` | PolicyKit integration | 128-1.1 |
|
||||
| `imagemagick` | image processing for palette wallpapers | 7.1.2.34-2.1 |
|
||||
| `ffmpeg` | media processing | 2:9.0.2-2.1 |
|
||||
|
||||
**Three rows this page used to claim are not installed**, verified with `pacman -Q` and
|
||||
`command -v`: `wlr-randr` (display information is `niri msg outputs` — no package needed, and
|
||||
[displays](niri/displays.md) owns that procedure), `qt6-multimedia` (audio reaches PipeWire
|
||||
through noctalia's own dependency), and `brightnessctl` (`/sys/class/backlight/amdgpu_bl1`
|
||||
exists and nothing on this box drives it — laptop brightness is a gap to close, not a feature
|
||||
to document). A dependency table is a claim about the machine: check it before writing it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+24
-11
@@ -8,7 +8,7 @@ tags:
|
||||
- provisioning
|
||||
- fleet
|
||||
- blankpad
|
||||
verified: 2026-10-20
|
||||
verified: 2026-10-21
|
||||
timestamp: 2026-10-20
|
||||
---
|
||||
|
||||
@@ -25,9 +25,13 @@ if both directions are checked.
|
||||
## 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.
|
||||
(AUR-only, installed through `yay`). **One parser reads them for both tools**:
|
||||
`scripts/lib/manifest.sh` — `sed 's/#.*//'` then the first field, so `#` comments trail freely and
|
||||
one token per line is the whole grammar; an unreadable manifest is a loud failure rather than an
|
||||
empty desired list. `tests/manifest-parser.bats` owns that contract. The two scripts carried
|
||||
verbatim copies of the body until 2026-10-21, which is exactly how a manifest line can come to mean
|
||||
two things to the two halves of the same check. An unknown `--host` gets the common manifest only:
|
||||
a wrong-host run is a no-op, not a hazard.
|
||||
|
||||
## Rebuild a workstation
|
||||
|
||||
@@ -48,7 +52,7 @@ undeclared additions (declare these): N ← exit 1 when N > 0
|
||||
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
|
||||
orphans ignored: <names> → pacman -Qtdq, never drift (see the law below)
|
||||
flatpak: <app ids> → reported only, install.sh installs no flatpaks
|
||||
```
|
||||
|
||||
@@ -78,15 +82,23 @@ 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
|
||||
**Known gap:** `aur.pkgs` is read for *every* host, so a desktop-only AUR package — the niri
|
||||
module `nirimod-git`, say, or 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 and stays
|
||||
undeclared (see the note in `packages/blankpad.pkgs`) 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`.
|
||||
Drift is `pacman -Qqe` — *explicitly* installed, therefore yours. `pacman -Qtdq` names packages
|
||||
installed **as a dependency** that nothing requires any more, and a package carries exactly one
|
||||
install reason, so the two sets cannot overlap (blankpad 2026-10-21: 206 explicit, 3 orphans, none
|
||||
in common). That is why nothing in `pkg-inventory.sh` subtracts the orphan list from the additions:
|
||||
the query is already the guard. Do not add such a subtraction — it cannot fire, and it would bury
|
||||
the invariant that matters, *an implicit install is never drift no matter when it arrived*, which
|
||||
`tests/pkg-inventory.bats` pins with a dependency installed after the baseline. What the
|
||||
`orphans ignored` line is for is the other half: dead weight on the machine that a manifest must
|
||||
never teach a fresh box to install.
|
||||
|
||||
## Two traps that produced this tool
|
||||
|
||||
@@ -102,5 +114,6 @@ install the same dead weight. They are printed as `orphans ignored` and excluded
|
||||
|
||||
- [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).
|
||||
- Tests: `tests/pkg-inventory.bats` (fixtures + stubbed `pacman`, no real package database touched)
|
||||
and `tests/manifest-parser.bats` (the grammar above, observed as names in / names out).
|
||||
- Refs #51.
|
||||
|
||||
@@ -8,7 +8,7 @@ tags:
|
||||
- rdna4
|
||||
- benchmarking
|
||||
- dankserver
|
||||
verified: 2026-10-20
|
||||
verified: 2026-10-21
|
||||
timestamp: 2026-10-20
|
||||
---
|
||||
|
||||
@@ -40,7 +40,10 @@ scripts/pcie-probe.sh --build-only # compile, never touch the cards
|
||||
|
||||
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.
|
||||
process holding `/dev/kfd`. Refusal is exit `3`, and it stops nothing. If the holder
|
||||
probe is missing the guard refuses too rather than assuming idle — see
|
||||
[seams](#exit-status-and-seams); `gpu_busy_percent` reads `0` for a service that is
|
||||
resident and idle, so a silent fallback is exactly the benchmark this law forbids.
|
||||
|
||||
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
|
||||
@@ -65,8 +68,11 @@ throughput at *that* size is what matters, not the big-transfer figure:
|
||||
- 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.
|
||||
Measure one device at a time (`--device`, a HIP ordinal). A run that lets the runtime
|
||||
pick several devices cannot attribute bandwidth to a card at all. The ordinal goes to
|
||||
the probe as an argument and **the driver never sets `HIP_VISIBLE_DEVICES`**: masking
|
||||
the device *and* passing its real ordinal asks `hipSetDevice` for a card the runtime
|
||||
has hidden, so every card but ordinal 0 fails to measure at all. One selector only.
|
||||
|
||||
## The trap carried in the sources
|
||||
|
||||
@@ -79,14 +85,18 @@ 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.
|
||||
`0` measured · `2` usage or no sources · `3` refused, GPU busy or holders unknowable ·
|
||||
`4` no HIP toolchain or build failed · `5` a probe ran and 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. A probe that dies (the segfault above, an unfundable
|
||||
allocation) exits `5` and names which probe on which device — a sweep that quietly
|
||||
swallowed the failure would report success with half its rows missing.
|
||||
|
||||
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.
|
||||
`PCIE_PROBE_FUSER`, `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.
|
||||
|
||||
@@ -38,10 +38,10 @@ read afterwards by a short-lived connection. That is what this is.
|
||||
|
||||
```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 status [NAME] # one line per job; names literal, never globs
|
||||
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 tail [NAME] # follow one job, or all; exit 1 if nothing to follow
|
||||
remote-job.sh watch # refreshing status, for a spare pane
|
||||
```
|
||||
|
||||
@@ -77,6 +77,7 @@ can be made from a fresh session on a machine that rebooted underneath 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 |
|
||||
| `tail` with no job, or a name that is a glob | exit 1 saying there is nothing to follow; names are matched literally, never expanded |
|
||||
| Job dir missing | created on first use |
|
||||
|
||||
Pinned by `tests/remote-job.bats` (black-box: output, exit codes, sentinel
|
||||
|
||||
@@ -18,8 +18,8 @@ btop
|
||||
|
||||
# system / fleet tooling
|
||||
yay # AUR helper bootstrap: ships in the cachyos repo; aur.pkgs installs need it
|
||||
git-lfs # LFS smudge/clean for tracked wallpapers (see .gitattributes)
|
||||
swayidle # idle lock + lid policy via swayidle-lock.service unit
|
||||
git-lfs # cloning LFS-backed upstreams (Hugging Face model/dataset repos); this repo tracks zero LFS files — .gitattributes is idle config
|
||||
swayidle # idle lock watcher via swayidle-lock.service; suspend locking is noctalia's sleep inhibitor
|
||||
|
||||
# desktop apps
|
||||
flameshot # screenshot tool; bound to CTRL+Shift+1/2/3 in niri keybinds
|
||||
|
||||
+32
-12
@@ -120,9 +120,20 @@ done
|
||||
|
||||
if [ "$MODE" != "check" ] && [ -d "$PKGS_DIR/systemd-user/.config/systemd/user" ]; then
|
||||
systemctl --user daemon-reload || true
|
||||
for unit in "$PKGS_DIR"/systemd-user/.config/systemd/user/*.service; do
|
||||
UNITS_DIR="$PKGS_DIR/systemd-user/.config/systemd/user"
|
||||
# Services AND timers. A timer is the schedule that makes a oneshot service run at all, and it
|
||||
# carries no ExecStart of its own — the binary that decides whether the unit belongs on this
|
||||
# box lives in the service it activates. Walking *.service only deployed live-sync.service on
|
||||
# every rebuild and never armed live-sync.timer: the heartbeat was present and never ticked.
|
||||
for unit in "$UNITS_DIR"/*.service "$UNITS_DIR"/*.timer; do
|
||||
[ -e "$unit" ] || continue
|
||||
bin="$(sed -n 's/^ExecStart=\([^ ]*\).*/\1/p' "$unit" | head -1)"
|
||||
name="$(basename "$unit")"
|
||||
probe="$unit"
|
||||
if [ "${name##*.}" = timer ]; then
|
||||
act="$(sed -n 's/^Unit=\(.*\)$/\1/p' "$unit" | head -1)"
|
||||
probe="$UNITS_DIR/${act:-"${name%.timer}.service"}"
|
||||
fi
|
||||
bin="$(sed -n 's/^ExecStart=\([^ ]*\).*/\1/p' "$probe" 2>/dev/null | 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
|
||||
@@ -132,22 +143,31 @@ if [ "$MODE" != "check" ] && [ -d "$PKGS_DIR/systemd-user/.config/systemd/user"
|
||||
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
|
||||
# as for a `disabled` one. Starting something already installed is how a deploy locks a
|
||||
# live session — swayidle-lock sits enabled/inactive on blankpad and `--now` arms it
|
||||
# mid-conversation. Only a unit nothing has installed yet gets started. Refs #44.
|
||||
state="$(systemctl --user is-enabled "$name" 2>/dev/null || true)"
|
||||
case "$state" in
|
||||
"" | disabled)
|
||||
systemctl --user enable --now "$name" || true
|
||||
echo "ENABLE $name (ExecStart resolves here; nothing running it yet)" ;;
|
||||
*)
|
||||
systemctl --user enable "$name" || true
|
||||
echo "ENABLE $name (already $state; not started — a live session decides what runs)" ;;
|
||||
esac
|
||||
if ! grep -q '^\[Install\]' "$unit"; then
|
||||
# systemd refuses to enable these: no [Install] section means nothing can want the unit
|
||||
# at login, and live-sync.service has none on purpose — its timer pulls it in. The old
|
||||
# branch swallowed that failure and printed ENABLE anyway, so the one line a rebuild is
|
||||
# checked by was false on every box. The section is readable right here, so do not make
|
||||
# the call at all. Refs #60.
|
||||
echo "INERT $name (state=${state:-none}; no [Install] section — another unit arms it, enable cannot)"
|
||||
elif [ -z "$state" ] || [ "$state" = disabled ]; then
|
||||
if systemctl --user enable --now "$name"; then
|
||||
echo "ENABLE $name (ExecStart resolves here; nothing running it yet)"
|
||||
else
|
||||
echo "INERT $name (enable --now failed — systemd's message above says why)"
|
||||
fi
|
||||
elif systemctl --user enable "$name"; then
|
||||
echo "ENABLE $name (already $state; not started — a live session decides what runs)"
|
||||
else
|
||||
echo "INERT $name (already $state; enable failed — systemd's message above says why)"
|
||||
fi
|
||||
else
|
||||
echo "SKIP $(basename "$unit") (ExecStart '$bin' absent on this host)"
|
||||
echo "SKIP $name (ExecStart '$bin' absent on this host)"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
+20
-10
@@ -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; }
|
||||
|
||||
+3
-4
@@ -17,7 +17,8 @@
|
||||
# otherwise sudo prompts normally. The value is never stored in this repo.
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(git -C "$(dirname "$(realpath "$0")")" rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
HERE="$(dirname "$(realpath "$0")")"
|
||||
REPO="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
REPO="${INSTALL_REPO:-$REPO}"
|
||||
[ -n "$REPO" ] && [ -d "$REPO/packages" ] || { echo "install.sh: no packages/ under ${REPO:-<repo>}" >&2; exit 1; }
|
||||
|
||||
@@ -36,9 +37,7 @@ SUDO() {
|
||||
else sudo "$@"; fi
|
||||
}
|
||||
|
||||
read_manifest() { # $1 = manifest path; emits package names, strips comments/blanks
|
||||
sed 's/#.*//' "$1" | tr -d '\r' | awk '{if ($1) print $1}'
|
||||
}
|
||||
source "$HERE/lib/manifest.sh" # the same parser pkg-inventory.sh reads with
|
||||
# ---- desired-minus-installed delta ------------------------------------------
|
||||
PKGS=()
|
||||
for m in common.pkgs "$HOST.pkgs"; do
|
||||
|
||||
+13
-1
@@ -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()
|
||||
|
||||
+31
-4
@@ -12,7 +12,19 @@ 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
|
||||
|
||||
# Names that a wikilink may point at: exactly the pages pages() reports. Deriving this
|
||||
# once keeps linting and link resolution from disagreeing about what exists.
|
||||
live = {p.stem for p in pages()}
|
||||
|
||||
for p in pages():
|
||||
rel = p.relative_to(kb)
|
||||
text = p.read_text(errors="replace")
|
||||
lines = text.splitlines()
|
||||
@@ -38,9 +50,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)
|
||||
@@ -50,9 +74,12 @@ for p in sorted(kb.rglob("*.md")):
|
||||
if not Path(target).is_file():
|
||||
violations.append(f"{rel}: broken relative link -> {m.group(1)}")
|
||||
for m in re.finditer(r"\[\[([^\]|#]+)", text):
|
||||
# wikilinks resolve by filename anywhere in the KB (Obsidian semantics)
|
||||
# wikilinks resolve by filename anywhere in the KB (Obsidian semantics) — but only
|
||||
# among pages that BELONG to the bundle. A raw rglob also walked .AGENTTRASH/, so a
|
||||
# link to a quarantined page stayed "valid" here while pointing at nothing on every
|
||||
# other machine (the trash is gitignored). Same exclusion as pages(), one source.
|
||||
name = m.group(1).strip().rsplit("/", 1)[-1]
|
||||
if not list(kb.rglob(name + ".md")):
|
||||
if name not in live:
|
||||
violations.append(f"{rel}: broken wikilink -> [[{m.group(1).strip()}]]")
|
||||
|
||||
for v in violations:
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/lib/manifest.sh — the one parser for packages/*.pkgs
|
||||
#
|
||||
# install.sh walks desired → installed; pkg-inventory.sh walks installed → declared. Both read the
|
||||
# same hand-written manifests, so both must agree on what a line MEANS: if one forgives an inline
|
||||
# comment and the other does not, the same name is simultaneously "desired" and "drift", and the
|
||||
# pair stops being a check on the machine. Source it — do not paste the body into a caller again
|
||||
# (Refs #60: it was pasted twice, in install.sh and in pkg-inventory.sh).
|
||||
#
|
||||
# Contract, and tests/manifest-parser.bats is that contract:
|
||||
# name first field of a line, emitted as one bare token — callers hand it to pacman
|
||||
# comment `#` and everything after it is dropped, whole-line or trailing
|
||||
# CRLF stripped (the manifests are edited on more than one box)
|
||||
# blank empty and whitespace-only rows vanish
|
||||
# unreadable non-zero exit, no names: a silent empty list reads as "nothing desired", and a
|
||||
# rebuild would then install nothing while reporting success
|
||||
#
|
||||
# Sourced, never executed; callers own `set -euo pipefail`.
|
||||
|
||||
read_manifest() { # $1 = manifest path; prints one package name per line
|
||||
[[ -r "$1" ]] || { printf 'read_manifest: cannot read %s\n' "$1" >&2; return 2; }
|
||||
sed 's/#.*//' "$1" | tr -d '\r' | awk '{if ($1) print $1}'
|
||||
}
|
||||
+37
-8
@@ -15,8 +15,12 @@
|
||||
#
|
||||
# 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.
|
||||
# --device N measure HIP device ordinal N; repeat for each card. Without it the probes run
|
||||
# once with the environment's own device selection — one shared run cannot
|
||||
# attribute bandwidth. The ordinal is passed to the probes as argv, which is the
|
||||
# only selector that survives contact with HIP: HIP_VISIBLE_DEVICES=1 makes
|
||||
# ordinal 0 the sole visible card, so masking AND passing N asks the probe for a
|
||||
# device that no longer exists. The mask is deliberately not set here.
|
||||
# --build-only compile and stop; never touches the cards.
|
||||
# --force measure even though the guard says the GPU is busy.
|
||||
#
|
||||
@@ -27,10 +31,11 @@
|
||||
# PCIE_PROBE_SYSFS drm sysfs root (/sys/class/drm)
|
||||
# PCIE_PROBE_KFD KFD device node (/dev/kfd)
|
||||
# PCIE_PROBE_HIPCC compiler (hipcc)
|
||||
# PCIE_PROBE_FUSER KFD holder probe (fuser)
|
||||
# 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.
|
||||
# Exit status: 0 measured · 2 usage or missing sources · 3 REFUSED, GPU busy or unknowable ·
|
||||
# 4 no toolchain or build failed · 5 a probe ran and failed.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
@@ -39,6 +44,7 @@ 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}"
|
||||
FUSER="${PCIE_PROBE_FUSER:-fuser}"
|
||||
MAX_BUSY="${PCIE_PROBE_MAX_BUSY:-5}"
|
||||
|
||||
force=0
|
||||
@@ -63,9 +69,19 @@ 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}"
|
||||
if [[ -e "$KFD" ]]; then
|
||||
if 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}"
|
||||
else
|
||||
# No fuser (psmisc) means the KFD holders are unknowable — and gpu_busy_percent reads 0
|
||||
# for a service that is resident but idle. Guessing "idle" here is how you end up
|
||||
# benchmarking beside a live endpoint, so the guard fails closed instead.
|
||||
printf 'pcie-probe: REFUSED — cannot see who holds %s: `%s` not found (psmisc)\n' "$KFD" "$FUSER" >&2
|
||||
printf ' install psmisc, or pass --force if you have decided the contention is\n' >&2
|
||||
printf ' acceptable and read gpu_busy_percent yourself first.\n' >&2
|
||||
exit 3
|
||||
fi
|
||||
fi
|
||||
for card in "$SYSFS"/card*; do
|
||||
[[ -f "$card/device/gpu_busy_percent" ]] || continue
|
||||
@@ -111,7 +127,11 @@ for card in "$SYSFS"/card*; do
|
||||
done
|
||||
|
||||
# ---- measure, per device --------------------------------------------------------
|
||||
# One selector only (see --device above), and every probe's exit status is honoured: a probe
|
||||
# that dies must not let the sweep report success. Failures are collected so the remaining
|
||||
# cards are still measured — losing the second card's numbers is how a campaign lies.
|
||||
(( ${#devices[@]} > 0 )) || devices=(default)
|
||||
failures=()
|
||||
for d in "${devices[@]}"; do
|
||||
if [[ $d == default ]]; then
|
||||
printf '\n########## engine device selection ##########\n'
|
||||
@@ -121,6 +141,15 @@ for d in "${devices[@]}"; do
|
||||
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
|
||||
if [[ $d == default ]]; then
|
||||
"$bin" || failures+=("$name (engine selection)")
|
||||
else
|
||||
"$bin" "$d" || failures+=("$name (device $d)")
|
||||
fi
|
||||
done
|
||||
done
|
||||
if (( ${#failures[@]} > 0 )); then
|
||||
printf 'pcie-probe: %d probe(s) failed:\n' "${#failures[@]}" >&2
|
||||
printf ' %s\n' "${failures[@]}" >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
@@ -19,11 +19,19 @@
|
||||
# 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
|
||||
# orphans ignored: <names> pacman -Qtdq — never drift, 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.
|
||||
|
||||
# Drift is `pacman -Qqe`: explicitly installed, therefore something you asked for and a rebuild
|
||||
# must declare. `pacman -Qtdq` orphans are packages installed AS A DEPENDENCY and no longer
|
||||
# required, and a package carries exactly one install reason, so the two sets are disjoint by
|
||||
# construction (blankpad, 2026-10-21: 206 explicit, 3 orphans, none in common). Nothing here
|
||||
# subtracts the orphan list from the additions and nothing should — the query is the guard. Do not
|
||||
# add such a subtraction: it cannot fire, and it would hide the real invariant (implicit installs
|
||||
# are never drift, whenever they arrived) behind a line that looks like protection.
|
||||
#
|
||||
# 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
|
||||
@@ -42,23 +50,22 @@ MODE="report"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--emit) MODE="emit"; shift ;;
|
||||
-h | --help) sed -n '2,40p' "$0"; exit 0 ;;
|
||||
-h | --help) awk '/^set /{exit} NR>1' "$0"; exit 0 ;;
|
||||
*) echo "usage: pkg-inventory.sh [--emit]" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
HERE="$(dirname "$(realpath "$0")")"
|
||||
REPO="${PKGINV_REPO:-}"
|
||||
if [[ -z "$REPO" ]]; then
|
||||
REPO="$(git -C "$(dirname "$(realpath "$0")")" rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
REPO="$(git -C "$HERE" 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}'
|
||||
}
|
||||
source "$HERE/lib/manifest.sh" # the same parser install.sh installs with
|
||||
# ---- 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
|
||||
|
||||
+20
-6
@@ -51,13 +51,21 @@ case "${1:-}" in
|
||||
;;
|
||||
|
||||
status)
|
||||
# Job names are data, never patterns. `for n in $names` word-split AND globbed: a job named
|
||||
# `x*` was expanded against the caller's working directory, so the report described unrelated
|
||||
# files there as jobs and never mentioned the one that ran. Splitting stays (several names in
|
||||
# one argument is the documented call shape); expansion does not.
|
||||
names=()
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
names="$2"
|
||||
read -r -a names <<< "$2"
|
||||
else
|
||||
names=$(cd "$JOBS" && ls *.out 2>/dev/null | sed 's/\.out$//' | sort)
|
||||
while IFS= read -r -d '' f; do
|
||||
names+=("$(basename "$f" .out)")
|
||||
done < <(find "$JOBS" -maxdepth 1 -name '*.out' -print0 2>/dev/null | sort -z)
|
||||
fi
|
||||
if [[ ${#names[@]} -eq 0 ]]; then echo " (no jobs)"; exit 0; fi
|
||||
pending=0
|
||||
for n in $names; do
|
||||
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 ' ')"
|
||||
@@ -69,7 +77,6 @@ case "${1:-}" in
|
||||
pending=1
|
||||
fi
|
||||
done
|
||||
if [[ -z "$names" ]]; then echo " (no jobs)"; exit 0; fi
|
||||
exit "$pending"
|
||||
;;
|
||||
|
||||
@@ -95,8 +102,15 @@ case "${1:-}" in
|
||||
|
||||
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
|
||||
# Both branches check for something to follow first: an unexpanded `*.out` handed to tail
|
||||
# reports a missing *file*, which reads as a broken tool instead of an empty harness.
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
[[ -f "$JOBS/$2.out" ]] || { echo "remote-job: no job '$2' (no $JOBS/$2.out)" >&2; exit 1; }
|
||||
exec tail -f "$JOBS/$2.out"
|
||||
fi
|
||||
mapfile -d '' -t follow < <(find "$JOBS" -maxdepth 1 -name '*.out' -print0 2>/dev/null | sort -z)
|
||||
(( ${#follow[@]} )) || { echo "remote-job: no jobs in $JOBS yet" >&2; exit 1; }
|
||||
exec tail -f "${follow[@]}"
|
||||
;;
|
||||
|
||||
watch)
|
||||
|
||||
+59
-1
@@ -37,7 +37,22 @@ if [ "$2" = is-enabled ]; then
|
||||
printf '%s\n' "$state"
|
||||
case "$state" in disabled | "") exit 1 ;; *) exit 0 ;; esac
|
||||
fi
|
||||
exit 0
|
||||
if [ "$2" = enable ]; then
|
||||
# Modelled, not stubbed flat: systemd refuses to enable a unit whose file has no [Install]
|
||||
# section, and the deployed file is what it reads. A stub that always says yes hides the
|
||||
# one case this repo actually ships (live-sync.service is driven by its timer).
|
||||
target=""
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
*.service | *.timer | *.socket) target="$a" ;;
|
||||
esac
|
||||
done
|
||||
file="$HOME/.config/systemd/user/${target##*/}"
|
||||
if [ -e "$file" ] && ! grep -q '^\[Install\]' "$file"; then
|
||||
printf 'The unit files have no installation config (WantedBy=, RequiredBy=, Also= settings in the [Install] section). This means they are not meant to be enabled using this command.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
STUB
|
||||
chmod +x "$TEST_TMP/bin/systemctl"
|
||||
export PATH="$TEST_TMP/bin:$PATH"
|
||||
@@ -57,6 +72,21 @@ systemd_unit() {
|
||||
> "$REPO/dotfiles/systemd-user/.config/systemd/user/$1"
|
||||
}
|
||||
|
||||
# systemd_unit_no_install <name> <ExecStart> — a unit nothing can enable (live-sync.service:
|
||||
# it is pulled in by live-sync.timer, so it carries no [Install] section on purpose).
|
||||
systemd_unit_no_install() {
|
||||
mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user"
|
||||
printf '[Unit]\nDescription=%s\n[Service]\nType=oneshot\nExecStart=%s\n' "$1" "$2" \
|
||||
> "$REPO/dotfiles/systemd-user/.config/systemd/user/$1"
|
||||
}
|
||||
|
||||
# systemd_timer <name> <service-name> — a schedule; it has no ExecStart of its own.
|
||||
systemd_timer() {
|
||||
mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user"
|
||||
printf '[Unit]\nDescription=%s\n[Timer]\nOnCalendar=hourly\nPersistent=true\n[Install]\nWantedBy=timers.target\n' "$1" \
|
||||
> "$REPO/dotfiles/systemd-user/.config/systemd/user/$1"
|
||||
}
|
||||
|
||||
@test "generated theme output is never adopted into a symlink" {
|
||||
repo_file "ghostty/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
||||
live_file "$HOME/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
||||
@@ -152,3 +182,31 @@ systemd_unit() {
|
||||
grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG"
|
||||
! grep -q -- '--now' "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "a unit nothing can enable is reported as what it is, never as ENABLE" {
|
||||
# `systemctl --user enable live-sync.service` fails on every deploy: the file has no
|
||||
# [Install] section (its timer arms it), and systemd says so with a non-zero exit. The
|
||||
# deploy swallowed that and printed ENABLE, so the one line the operator reads to learn
|
||||
# what a rebuild armed was false on every box, on the repo's own heartbeat.
|
||||
systemd_unit_no_install heartbeat.service /bin/sh
|
||||
printf 'linked\n' > "$UNIT_STATE"
|
||||
run "$REPO/scripts/deploy.sh"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"heartbeat.service"*"[Install]"* ]]
|
||||
[[ "$output" != *"ENABLE heartbeat.service"* ]]
|
||||
# No futile call: the section is readable from the file being deployed.
|
||||
! grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "a timer in the tier is armed, because a service on its own has no schedule" {
|
||||
# The loop walked *.service only. On a rebuilt box live-sync.service is deployed and the
|
||||
# timer is never enabled, so the heartbeat that commits machine state simply does not run
|
||||
# — and the deploy report says nothing about it.
|
||||
systemd_unit heartbeat.service /bin/sh
|
||||
systemd_timer heartbeat.timer heartbeat.service
|
||||
printf 'disabled\n' > "$UNIT_STATE"
|
||||
run "$REPO/scripts/deploy.sh"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" != *"SKIP heartbeat.timer"* ]]
|
||||
grep -q 'systemctl --user enable --now heartbeat.timer' "$STUB_LOG"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+49
@@ -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)"* ]]
|
||||
}
|
||||
@@ -91,6 +91,57 @@ 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 "a wikilink must not resolve into the quarantine" {
|
||||
# Obsidian resolves [[name]] by filename anywhere under the vault, and the resolver copied
|
||||
# that with a raw rglob — so [[log]] stayed "valid" by landing on the copy waiting in
|
||||
# .AGENTTRASH/. That is #57's failure wearing a green check: the page is out of circulation
|
||||
# until a pruning session with Frank, and it is not even tracked, so on another machine the
|
||||
# link points at nothing while the gate here says it is fine.
|
||||
mkdir -p "$KB/.AGENTTRASH"
|
||||
printf 'no frontmatter, quarantined\n' > "$KB/.AGENTTRASH/log.md"
|
||||
printf -- '---\ntype: reference\n---\nSee [[log]] for the history.\n' > "$KB/good.md"
|
||||
run bash "$LINT" "$KB"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"broken wikilink -> [[log]]"* ]]
|
||||
}
|
||||
|
||||
@test "a wikilink to a live page still resolves, in any directory" {
|
||||
# The narrowing above must cost nothing: filename-anywhere semantics is the point.
|
||||
printf -- '---\ntype: reference\n---\nSee [[good]].\n' > "$KB/sub/other.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
|
||||
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/manifest-parser.bats — the one manifest parser, shared by install.sh and pkg-inventory.sh
|
||||
#
|
||||
# `packages/*.pkgs` is hand-written, so the parser's whole job is to forgive the ways a hand-edited
|
||||
# list is wrong. The same function decides what install.sh tries to install and what pkg-inventory
|
||||
# calls drift, so a manifest line must mean the same thing to both — which is why it lives in one
|
||||
# file instead of being pasted into each caller (Refs #60: it was pasted twice).
|
||||
#
|
||||
# Observed through the function's exit status and stdout only:
|
||||
# names first field of a line, one bare token; `#` and everything after it is a comment
|
||||
# CRLF tolerated (the manifests get edited on more than one box)
|
||||
# blank lines vanish
|
||||
# unreadable non-zero with no names — an empty desired list must never be a silent success,
|
||||
# because "nothing desired" is exactly what a rebuild would install
|
||||
|
||||
setup() {
|
||||
TEST_TMP="$(mktemp -d)"
|
||||
LIB="$BATS_TEST_DIRNAME/../scripts/lib/manifest.sh"
|
||||
}
|
||||
|
||||
teardown() { rm -rf "$TEST_TMP"; }
|
||||
|
||||
parse() { # $1 = manifest contents, verbatim
|
||||
printf '%s' "$1" > "$TEST_TMP/m.pkgs"
|
||||
run bash -c 'source "$1"; read_manifest "$2"' _ "$LIB" "$TEST_TMP/m.pkgs"
|
||||
}
|
||||
|
||||
@test "inline notes, full-line comments, blank rows and CRLF come out as bare names" {
|
||||
parse $'# desired state\r\nghostty # terminal\r\n\r\n \r\nzoxide\r\n'
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(printf '%s\n' "$output" | tr '\n' ' ')" = "ghostty zoxide " ]
|
||||
}
|
||||
|
||||
@test "only the first field of a line is a package" {
|
||||
# install.sh hands every emitted token straight to pacman, so a stray second token on a line
|
||||
# must never become an install target; whatever it is, the manifest did not declare it.
|
||||
parse $'neovim extra tokens here\n'
|
||||
[ "$output" = "neovim" ]
|
||||
}
|
||||
|
||||
@test "a comment that names a package is not a package" {
|
||||
parse $'# vlc\n#typora\nbun\n'
|
||||
[ "$output" = "bun" ]
|
||||
}
|
||||
|
||||
@test "an unreadable manifest fails loudly instead of yielding an empty desired list" {
|
||||
run --separate-stderr bash -c 'source "$1"; read_manifest "$2"' _ "$LIB" "$TEST_TMP/nope.pkgs"
|
||||
[ "$status" -ne 0 ]
|
||||
[ -z "$output" ]
|
||||
[[ "$stderr" == *nope.pkgs* ]]
|
||||
}
|
||||
+41
-2
@@ -6,7 +6,8 @@
|
||||
# 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.
|
||||
# Exit status: 0 measured · 2 usage · 3 refused (GPU busy) · 4 no toolchain / build failed ·
|
||||
# 5 a probe ran and 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.
|
||||
@@ -46,7 +47,16 @@ while [ $# -gt 0 ]; do [ "$1" = "-o" ] && out="$2"; shift; done
|
||||
: > "$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")"'
|
||||
# HIP rule, modelled because it is the trap: with HIP_VISIBLE_DEVICES set, the ONLY
|
||||
# valid ordinal is 0. A driver that masks the device and still passes its real ordinal
|
||||
# asks for a card the runtime cannot see — which is why the stub used to report the
|
||||
# env var instead of the argument, and could not see the collision.
|
||||
echo 'dev="${1:-0}"'
|
||||
echo 'if [ -n "${HIP_VISIBLE_DEVICES:-}" ] && [ "$dev" != 0 ]; then'
|
||||
echo ' printf "setDevice(%s) failed\n" "$dev" >&2; exit 1'
|
||||
echo 'fi'
|
||||
echo '[ "${STUB_PROBE_FAILS:-0}" = 1 ] && { echo "stub probe died mid-measurement" >&2; exit 1; }'
|
||||
echo 'echo "PROBE RAN dev=$dev bin=$(basename "$0")"'
|
||||
echo 'echo " slab 2.344 MiB: 31.4 GB/s"'
|
||||
} > "$out"
|
||||
chmod +x "$out"
|
||||
@@ -131,6 +141,35 @@ teardown() { rm -rf "$TEST_TMP"; }
|
||||
[[ "$output" == *"dev=1"* ]]
|
||||
}
|
||||
|
||||
@test "a device ordinal the environment mask would hide is still measured" {
|
||||
# Reproduction, not preference: HIP_VISIBLE_DEVICES=1 leaves ordinal 0 as the only
|
||||
# selectable card, so masking AND passing the ordinal asks hipSetDevice(1) for a
|
||||
# device the runtime cannot see. Per-card bandwidth was unobtainable that way.
|
||||
run "$SCRIPT" --device 0 --device 1
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"PROBE RAN dev=1"* ]]
|
||||
[[ "$output" != *"setDevice"* ]]
|
||||
}
|
||||
|
||||
@test "a probe that dies is reported as a failed run, not a green one" {
|
||||
# The header promises "0 measured". A probe that segfaults (the documented hazard on
|
||||
# this ROCm build) must not let the sweep exit 0.
|
||||
run env STUB_PROBE_FAILS=1 "$SCRIPT"
|
||||
[ "$status" -eq 5 ]
|
||||
[[ "$output" == *"failed"* ]]
|
||||
[[ "$output" == *"hip_smoke"* ]]
|
||||
}
|
||||
|
||||
@test "the guard refuses when it cannot see who holds the KFD, instead of assuming idle" {
|
||||
# Without fuser the /dev/kfd half of the guard cannot run, and gpu_busy_percent reads
|
||||
# 0 for a service that is resident but idle — a silent fallback would benchmark beside
|
||||
# a live endpoint, which is the one thing this tool's LAW forbids.
|
||||
run env PCIE_PROBE_FUSER="$TEST_TMP/nope-fuser" "$SCRIPT"
|
||||
[ "$status" -eq 3 ]
|
||||
[[ "$output" == *"fuser"* ]]
|
||||
[ ! -e "$BUILD/.built" ]
|
||||
}
|
||||
|
||||
@test "no probe sources is a usage-level error, not an empty success" {
|
||||
rm -f "$SRC"/*.hip
|
||||
run "$SCRIPT"
|
||||
|
||||
@@ -39,15 +39,18 @@ EOF
|
||||
[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-07T09:00:00-0800] [ALPM] installed libstray (1.0-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"
|
||||
# bun/nirimod-git: installed by hand afterwards, undeclared. python-hatchling and libstray:
|
||||
# installed as dependencies and now unrequired (pacman -Qtdq). libstray arrived AFTER the
|
||||
# baseline, so the date filter alone cannot keep it out of drift — only the explicit query can.
|
||||
printf '%s\n' base bun filesystem ghostty libfoo libstray 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"
|
||||
printf '%s\n' python-hatchling libstray > "$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"
|
||||
@@ -131,11 +134,34 @@ ADDITIONS() { section "^undeclared additions" "^installer-provided"; }
|
||||
[[ "$(section "^declared," "^orphans")" == *vlc* ]]
|
||||
}
|
||||
|
||||
@test "orphans are reported as ignored and never emitted" {
|
||||
@test "orphans are named on their own line, not mixed into the drift answer" {
|
||||
run "$SCRIPT"
|
||||
[[ "$output" == *orphans*python-hatchling* ]]
|
||||
[[ "$(section "^orphans" "^flatpak")" == *python-hatchling* ]]
|
||||
[[ "$(ADDITIONS)" != *python-hatchling* ]]
|
||||
}
|
||||
|
||||
@test "a dependency installed after the baseline is never drift, orphan or not" {
|
||||
# Drift means `pacman -Qqe` — you asked for this one. libstray arrived as a dependency
|
||||
# after install day and is now unrequired, so the baseline filter does not exclude it:
|
||||
# only the explicit query does. Swap -Qqe for -Qq and every dependency on the machine,
|
||||
# dead ones included, becomes a manifest line install.sh will reinstall on a rebuild.
|
||||
run "$SCRIPT"
|
||||
[[ "$(ADDITIONS)" != *libstray* ]]
|
||||
[[ "$output" == *"undeclared additions (declare these): 2"* ]]
|
||||
[[ "$(section "^orphans" "^flatpak")" == *libstray* ]]
|
||||
run "$SCRIPT" --emit
|
||||
[[ "$output" != *python-hatchling* ]]
|
||||
[[ "$output" != *libstray* ]]
|
||||
}
|
||||
|
||||
@test "--help prints the header and stops before the shell" {
|
||||
# The header IS the contract (report shape, exit status, seams). A hardcoded line range
|
||||
# that runs past the comment block hands the reader `set -euo pipefail` as if it were
|
||||
# documentation.
|
||||
run "$SCRIPT" --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Exit status"* ]]
|
||||
[[ "$output" != *"pipefail"* ]]
|
||||
[[ "$output" != *"LC_ALL"* ]]
|
||||
}
|
||||
|
||||
@test "--baseline moves packages installed on that date into the installer set" {
|
||||
|
||||
@@ -122,6 +122,31 @@ until_done() {
|
||||
[[ "$output" == *38* ]] # the tail, not the head
|
||||
}
|
||||
|
||||
@test "status treats a job name as a name, not a pattern" {
|
||||
"$SCRIPT" start 'x*' -- bash -c 'echo star'
|
||||
until_done 'x*'
|
||||
touch "$TEST_TMP/anchor"
|
||||
# The report is read from a directory full of unrelated files on purpose: a name that is
|
||||
# expanded as a glob resolves against the CALLER's cwd, so the tool would describe `anchor`
|
||||
# as a job and never mention the one that really ran.
|
||||
run bash -c 'cd "$1" && "$2" status "$3"' _ "$TEST_TMP" "$SCRIPT" 'x*'
|
||||
[[ "$output" == " x*"*"DONE rc=0"* ]]
|
||||
[[ "$output" != *anchor* ]]
|
||||
}
|
||||
|
||||
@test "tail on an empty harness and on a missing job both say so" {
|
||||
# An unexpanded `*.out` reaching tail is reported as a missing FILE, which reads as a broken
|
||||
# script; the honest answer is that there is nothing to follow yet.
|
||||
run timeout 5 "$SCRIPT" tail
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"no jobs"* ]]
|
||||
[[ "$output" != *".out"* ]]
|
||||
run timeout 5 "$SCRIPT" tail nosuchjob
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"nosuchjob"* ]]
|
||||
[[ "$output" != *"No such file"* ]]
|
||||
}
|
||||
|
||||
@test "an unknown verb is a usage error" {
|
||||
run "$SCRIPT" frobnicate
|
||||
[ "$status" -eq 2 ]
|
||||
|
||||
@@ -157,3 +157,48 @@ export API_KEY_FILE=/run/secrets/api_key'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"tests/other.bats"* ]]
|
||||
}
|
||||
|
||||
@test "a credential under a path git quotes is still read" {
|
||||
# core.quotePath is on by default, so `git diff --name-only` prints "caf\303\251/…" for a
|
||||
# non-ASCII directory. Feed that quoted string back to `git diff -- <path>` and the diff is
|
||||
# empty — and a gate that skips an unreadable file reports success. This is the fail-closed
|
||||
# promise breaking on its own terms, not a cosmetic quoting complaint.
|
||||
try_commit "café/postgres.env" 'POSTGRES_PASSWORD=hunter2corp2026'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"postgres.env"* ]]
|
||||
}
|
||||
|
||||
@test "a placeholder word elsewhere on the line does not exempt a live value" {
|
||||
# The rule is about the VALUE. `# example` in a comment says nothing about the token the
|
||||
# line assigns, but a whole-line placeholder filter lets the comment vouch for the secret.
|
||||
try_commit "app.env" 'API_TOKEN=hunter2corp2026 # example, placeholder, changeme'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"app.env"* ]]
|
||||
}
|
||||
|
||||
@test "each hit is attributed to the file it came from" {
|
||||
# The report is how the next agent finds the leak. If one file's last hit line runs into the
|
||||
# next file's name, the audit starts at a clean file and the leaker keeps its name.
|
||||
mkdir -p "$REPO/a" "$REPO/b"
|
||||
printf 'A_PASSWORD=hunter2corp2026\n' > "$REPO/a/one.env"
|
||||
printf 'B_PASSWORD=hunter2corp2026\n' > "$REPO/b/two.env"
|
||||
run git -C "$REPO" add -A
|
||||
run git -C "$REPO" commit -q -m "two leakers"
|
||||
[ "$status" -ne 0 ]
|
||||
printf '%s\n' "$output" | grep -qx ' a/one.env'
|
||||
printf '%s\n' "$output" | grep -qx ' b/two.env'
|
||||
! printf '%s\n' "$output" | grep -qE 'hunter2corp2026 +b/two\.env'
|
||||
}
|
||||
|
||||
@test "a documented fake id in prose is committable; a live-shaped id in the same file is not" {
|
||||
# Measured on blankpad 2026-10-21: the live-sync heartbeat failed three ticks in a row
|
||||
# because a managed-skill page documents how to write a secret gate and quotes AWS's own
|
||||
# placeholder id. A gate that blocks the machine tier over an inert example gets worked
|
||||
# around, and a workaround is the hole. The exemption reads the matched token, so a real
|
||||
# id in the same file still blocks.
|
||||
try_commit "docs.md" 'Inert fixtures look like AKIAIOSFODNN7EXAMPLE or AKIA0000000000000EXAMPLE.'
|
||||
[ "$status" -eq 0 ]
|
||||
try_commit "docs.md" 'Inert fixture AKIAIOSFODNN7EXAMPLE and a live one AKIAQZR4W7DBPCFG7V3Q.'
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"docs.md"* ]]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user