diff --git a/AGENTS.md b/AGENTS.md index 44f83c5..2358679 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,11 @@ git repos. > else; `GH_ORG="hausfold"`, `com.hausfold.*` and `hausfold.co` all stand. > > Plus `com.nebelhaus.*` / `org.nixos.pounce` bundle ids (**§4**), the state -> dirs (`~/.local/state/nebelhaus`, deliberately held — §2.2), and +> dirs (`~/.local/state/nebelhaus` and its three siblings — this row said +> "deliberately held — §2.2" until 2026-08-14, when **§11.3 moved all four to +> `haus` and left a symlink behind**, which is what keeps an older perch's +> install-marker contract working; the old paths still resolve, so a bare hit is +> a *compat* path, not a live one), and > `~/.cache/claude-worktrees/` (historical, stays). **The word alone tells you > nothing — read the hit before you touch it**, and grep the bare word > separately: a desktop file's top-level key is `{ haus = { … }; }`, with no dot diff --git a/notes/hausfold-rename.md b/notes/hausfold-rename.md index fd6a69d..b49fbcc 100644 --- a/notes/hausfold-rename.md +++ b/notes/hausfold-rename.md @@ -2082,7 +2082,7 @@ next to the org, not just here. | Do not sweep | Why | |---|---| | `/Library/Application Support/nebelhaus/perch.installed-from` | a **two-repo contract**: written by the rice (`nebelhaus/modules/perch/default.nix`), read by perch (`Perch/Platform/UpdateCheck.swift:118`, documented at `perch/docs/architecture-decisions/0003-*.md`). Rename it in one repo and perch stops recognising a rice install — it would start nudging rice users to download from GitHub. It is the *rice's* directory and the rice keeps its name. | -| `~/.local/state/nebelhaus/` | §2.2, deliberately held | +| `~/.local/state/nebelhaus/` | ⚠️ **§11.3 overtook this row on 2026-08-14**: all four state dirs moved to `haus` and left a **symlink** behind, which is exactly what keeps the two-repo contract above working from an older perch. Read a bare hit as a compat path, not a live one | | ⚠️ *(those two are **not** among the 649)* | neither matches the census regex — there's no repo name after the slash. A sweeper who reads "Tier D is ≥126 of the hits" and assumes the state dirs are inside that number is wrong: **they need their own grep**, which is §2.2's whole point | | `nebelhaus/modules/…` source paths in docs | the rice's own tree | | everything in `messages/` (24 hits) | §3.4 — it stays in the `nebelhaus` org | diff --git a/notes/options-roadmap.md b/notes/options-roadmap.md index f6579e3..ad5809d 100644 --- a/notes/options-roadmap.md +++ b/notes/options-roadmap.md @@ -99,6 +99,186 @@ already exist, and one it treated as a detail is the actual root blocker. > > What §7's repo routing means now: `nebelhaus` → `hausfold/haus`, and `web` > → the consolidated site repo. +> +> ★ **Amended 2026-08-15 (decision 10, haus#364): the desktop this document is +> about is called `hacker` now, and the banner's translation rule cannot reach +> the word that moved.** The rule above is *"read every `nebelhaus.*` below as +> `haus.*`"* — keyed on the dot, because in August the thing being renamed was an +> option namespace. Decision 10 renamed the **bare** word: the desktop +> `nebelhaus` is `desktops/hacker.nix` / `haus.desktops.hacker`, and every other +> surviving `nebelhaus` in the layer became `haus` (`mkNebelhaus`→`mkHaus`, the +> env vars, the state dirs). Counted over the body proper — §1 onward, so the +> number doesn't grow every time a pass writes the word — **it appears 137 times +> and the dotted rule translates 63 of them correctly**, plus two it matches and +> gets *wrong* (`nebelhaus.com` and a `.md` filename, both of which stay). Read +> the remaining seventy-odd in three senses, none of which that +> rule covers: **the repo** (`nebelhaus/modules/…`, +> `nebelhaus#NNN`) → `hausfold/haus`, unchanged as a citation; **the desktop** +> ("a non-dev nebelhaus", "publish nebelhaus configs") → `hacker`; and **the org +> or the domain** (`github.com/nebelhaus`, `nebelhaus.com`) → *stays*, forever. +> The plan of record is [`hausfold-rename.md`](./hausfold-rename.md) **§11**; +> `mkNebelhaus`, `desktopFiles.nebelhaus` and the four state-dir symlinks are +> deliberate compatibility seams, so nothing here is broken by the rename — it +> is only mis-named. The body stays un-rewritten for the reason given above. + + +> **Status, 2026-08-15 (twenty-third pass) — the desktop this whole document is +> about was renamed out from under it, and the audit that found that nearly read +> a stale checkout instead of the repo.** +> +> [haus#364](https://github.com/hausfold/haus/pull/364) merged four hours after +> the twenty-second pass's own PR: **decision 10 drops the name `nebelhaus`**, +> the desktop becomes **`hacker`**, and the naming banner above is amended rather +> than the body rewritten. Nothing this file tracks as work moved — the rename +> configures nothing differently, and its three compatibility seams mean no +> consumer has to act. What moved is every sentence here that says the word. +> +> ★ **The finding: a translation rule for a historical document is only as good +> as the token it keys on, and the bare word is the one no regex finds.** The +> 2026-08-08 banner told readers to translate `nebelhaus.*`; the rename that +> arrived translated `nebelhaus`. Those are different edits on the body's 137 +> hits — the dotted rule gets 63 right and two wrong — and the undotted word +> carries three referents at once (repo, desktop, org), so a reader "applying the +> banner" is right on **under half** of them and silently mis-reads the rest. +> ⚠️ The first draft of this paragraph said "203 hits, the rule reaches 82", +> which is **two different regions counted once each** — 203 is the whole file +> (including the fifteen times this very block says the word) and 82 was the +> body. A count is a claim, and a pair of counts is a claim about a scope; the +> fifth pass's rule (quote the command that produced the number) exists exactly +> because this is easy to get wrong while sounding precise. The workshop's own `AGENTS.md` had already written +> the trap down — *"grep the bare word separately: a desktop file's top-level key +> is `{ haus = { … }; }`, with no dot for a regex to find"* — for the layer, one +> repo over, in the same week. **A rule written for the code and not carried to +> the prose about the code is a rule that has to be learned twice.** +> +> ★ **Second, and it is about how these audits are run: "audit against the repos, +> not against memory" (§5.14's founding rule) has a third thing in it. The local +> checkout is memory wearing the repo's clothes.** `~/code/workshop/haus` still +> held `desktops/nebelhaus.nix`, with no `hacker.nix`, **46 minutes** after #364 +> merged (`holt child` branched this lane from `afc3b58`, the commit immediately +> before the merge) +> — so every question this pass put to the disk would have come back answered by +> yesterday, in the confident voice of a file that exists. It is the worst of the +> three sources precisely because it *looks* like the repo; memory at least knows +> it is memory. The signal is one command and already exists: `bench status` +> fetches before it compares `@{u}` (`bench:547`) and reports `↓ 1`. Two cheap +> mitigations, both now written into §5.14: **fetch, or read GitHub** — this pass +> switched to `gh api …/contents/…`, which cannot be stale — and **never let a +> pass cite a path on disk it hasn't dated**. +> +> ⚠️ Recorded because it happened while writing the sentence above: the first +> draft of that finding said `bench status` *never* fetches, from a `grep "git +> fetch"` that could not match `git -C "$dir" fetch -q --tags`. **A negative claim +> proved by a grep is only as strong as its pattern, and negatives are where +> patterns are weakest** — the same family as the twenty-second pass's +> generator-not-artifact row, and rows nine and ten of §5.14's table. +> +> ★ **Third, the one that shipped code: the lock ripple carries the binary and +> nothing carries the grammar it speaks.** `haus.pounce.items` validates its keys +> against a hand copy of pounce's `ItemTarget` grammar. pounce added a fourth +> address prefix — `shortcut:`, the Shortcuts library as launcher rows +> ([pounce#80](https://github.com/hausfold/pounce/pull/80), merged 2026-08-14 +> 21:03:50) — and haus's lock moved to that exact merge commit **seventy-nine +> seconds later** (657a3a2, `Update flake inputs (pounce perch)`, 21:05:09), so +> this layer has been +> shipping a daemon that accepts the key while its own module asserts the key +> *"is not an item key"*. The layer tells the user their valid key is a typo, at +> build time, in an assertion, quoting an error string that pounce's own copy had +> already outgrown. §5.9's box (b) asserts that validation as a *feature* +> and is amended there. +> +> **What makes it worth a ★ rather than a fix: the two mirrors sit in the same +> `let`, and only the smaller one was ever argued about.** `builtinModes` carries +> a comment weighing its own risk — *"Six strings, so it's the size of mirror +> that's worth its risk"* — and the if-chain three lines below it, which +> enumerates the prefixes inline rather than as a list, carries none. +> The comment sized the risk of the **values** it mirrors and never the risk of +> the **set** it enumerates, which is the twenty-second pass's blind-spot finding +> one level up: *a check that varies one option is blind to what hides behind a +> second one* becomes *a mirror that reasons about the axis it varies is blind to +> the axis it fixes*. Ask it of every mirror in the family: **which dimension of +> this copy did I decide was closed?** +> +> ★ **And the seventh check, which arrives from a direction §5.14 never +> proposed.** `pounce-item-grammar` diffs the mirror against **the locked +> pounce's own source** — `ItemSettings.swift`, at the rev `flake.lock` pins, not +> at pounce's main — so the question it answers is never "what does pounce do +> now" but "does the grammar we validate against match the binary we install". +> §5.14's reason 1 says every cross-repo seam here got fixed by making the +> upstream repo *emit* something, and that the doc-side seam is still prose on +> both sides; this is the third option nobody wrote down: **a flake input already +> IS the upstream repo, pinned at the rev you ship, and its source is readable as +> data without asking that repo for anything.** It cost no change in pounce. It +> is in the all-systems check set, so unlike `accent-reach` it fires on CI as +> well as here. **Ten ★ findings, seven checks.** Open as +> [haus#365](https://github.com/hausfold/haus/pull/365) — with an owner and a +> number, per the twentieth pass's rule. +> +> ⚠️ **And the assurance pass earned its place again: the first version of that +> check mirrored pounce's ERROR SENTENCE, which is not where pounce's grammar +> lives.** Only `ItemTarget.modes` is single-sourced in pounce (its "can't drift" +> comment sits on that line and covers it alone); the prefixes are `hasPrefix` +> literals in `parse` and a hand-written restatement in `problem` — two copies, +> so the mirror was a mirror of a mirror. A prefix added to `parse` and forgotten +> in the sentence would have left both repos wrong **in agreement**, with the +> check green: this very bug, recurring undetected. The same read found that a +> check pinning the mirror pins nothing about the **validator** — the cheapest +> way to green it is appending one string to the data file, which leaves +> `itemKeyProblem` rejecting the key its own error message now advertises (fixed +> with an assertion that every shape has a sample and every sample survives), +> and that the check's "don't delete me" guard was unreachable under the +> builder's `set -e -o pipefail`. **A tripwire is worth exactly what its +> weakest sample is worth, and three of those four defects were the check being +> green for the wrong reason** — the twenty-second pass's fourth finding, +> arriving as a class rather than an instance. +> +> ★ **Fourth, and it is this section's own lesson wearing a different repo's +> clothes: haus's CI census carries the instruction to keep itself honest, and +> checking it beat appending to it four to nothing.** `.github/workflows/check.yml` +> enumerates which checks run on CI's Linux runner, and says in the file: *"Keep +> this census honest when a check is added: it is this repo's only record of what +> CI actually covers, and it went one stale before anyone noticed."* The +> twenty-third pass's first instinct was to add one name and bump "ten" to +> "eleven" — which would have been correct and would have left **four** existing +> errors in place: the count was already one short, `sill-rc-executable` was +> filed as darwin-only when it is portable, `desktop-projection` as portable when +> it is darwin-gated, and `sill-plugins-executable` had never been listed at all. +> Both lists are re-derived now from the one line that decides the split +> (`optionalAttrs (hasSuffix "-darwin" system)`), and the portable half is a +> table rather than a paragraph, because a paragraph is what made four errors +> invisible. **Same shape as the ledger correction on the twenty-second pass, in +> another repo, found the same way — by re-deriving instead of reading.** The +> rule generalises past both: *a prose census of a machine-readable fact rots in +> every direction at once, so the instruction to keep it honest has to mean +> re-derive it, not extend it.* +> +> Housekeeping: two rows added to §5.14's shapes table (stale checkout, negative +> claim by grep), the ledger paragraph appended there, and §5.9's two pounce +> boxes amended — its address space is four prefixes now, not three. Two +> rename-side corrections fell out of the assurance read and are fixed in the +> same commit, both shape 2: `AGENTS.md` still called the four state dirs +> "deliberately held" and `hausfold-rename.md` §2.2's do-not-sweep table still +> agreed with it — left behind by the very commit (workshop#372) that amended +> the row four lines above, which is how a partial sweep fails. The +> instructive part is *where* it was found: the pass quoted `AGENTS.md` +> approvingly two lines below a line its own finding falsified — **a document you +> are citing is not a document you are reading.** +> +> **Verified:** `nix flake check` green in the haus lane (**24** checks), +> `nixfmt --check` clean, `docs/site-data/options.json` regenerated and +> committed. `pounce-item-grammar` mutation-checked in every direction — +> deleting `shortcut:` from the data file reproduces the exact pre-fix +> error string and the check names the missing prefix; corrupting a mode name +> names that instead; an unsampled shape and a sampled-but-still-rejected shape +> each fail with their own sentence; a dropped mode caption names the mode. The +> assertions were proved live rather than by reading: a system evaluating two +> `shortcut:` items builds, and a `shortcutz:` typo still fails, now with +> pounce's wording word for word. No `bench try switch` — no drawn output moves. +> **The two assurance passes (one per repo, clean context, diff plus AGENTS.md) +> found eleven things between them, four of them ≥3/5** — including both count +> claims in this block, a check that could go green while the validator it +> protects stays broken, and the CI census above. Every one of them was a claim +> about code, made from a document, by the session that wrote the code. > **Status, 2026-08-15 (twenty-second pass) — the small `sans` is built, and the @@ -3075,7 +3255,9 @@ break timer · storage pressure · NAS reachability · world clocks. §5.2 for the app-side seam that made the second one possible. - [x] **pounce side: `config.json` grew an `items` map** (pounce#43), keyed by the frecency key so commands, apps and built-in modes share **one address space** - (`cmd:` / `app:` / `mode:`), each taking `enabled` / `alias` / `hotkey`. The + (`cmd:` / `app:` / `mode:` — **four prefixes since pounce#80, 2026-08-14**: + `shortcut:` addresses a Shortcuts-library entry), each taking + `enabled` / `alias` / `hotkey`. The design fork recorded there was *one schema now* vs *a key per stage*, resolved to one **because these ripple into `nebelhaus/modules/pounce` either way** — i.e. the rice-side option was a known consequence, not an afterthought. @@ -3095,6 +3277,19 @@ break timer · storage pressure · NAS reachability · world clocks. registers first wins, and it isn't always the same one). What it cannot check is whether `cmd:` names a command that exists, because command scripts are discovered at runtime — so that half stayed pounce's job. + ⚠️ **Amended on the twenty-third pass, and it is shape 2 (a closed claim + falsified) at its most expensive: that validation is a MIRROR, and a mirror + of another repo's grammar fails in the direction this box never considered.** + pounce grew a fourth prefix (`shortcut:`, pounce#80) and haus's lock + moved to it two minutes later, so the layer spent a day asserting that a key its + own daemon accepts *"is not an item key"* — validation whose failure mode + is not a missed typo but a **refused valid key**, in the user's face, at + build time. Fixed and pinned to the *locked* pounce's source by + `pounce-item-grammar` ([haus#365](https://github.com/hausfold/haus/pull/365); + see the twenty-third pass's block at the top). The + generalisable half is in §5.14: this box argued the risk of the small + mirror beside it (`mode:` names) and never the risk of the enumeration + itself. **(c)** the rice generates the WHOLE map and never round-trips: on the rice `config.json` is a `/nix/store` symlink so Nix is the only writer, but under Homebrew it's a plain writable file a future settings UI edits. Designing @@ -3560,6 +3755,8 @@ catch: | open box, blocker already removed *(added on the twenty-first pass — §5.3's `sans`)* | re-reading the WHY beside a box, not just the box | | audit invents a regression *(fourteenth pass — it was written as a one-row table below and never folded in here, which is the hazard this table exists to fix; folded in on the twenty-second)* | a clean-context reader who re-derives the evidence | | claim about a generated artifact, derived from its generator *(twenty-second pass — §5.3's `sans`)* | reading the artifact the check actually samples | +| claim about a repo, read from the LOCAL CHECKOUT *(twenty-third pass — haus's checkout sat one merge behind while the audit ran)* | fetching first (`bench status` does), or reading GitHub | +| a NEGATIVE claim ("X never happens") proved by a grep *(twenty-third pass — "`bench status` never fetches", from a pattern that couldn't match `git -C "$dir" fetch`)* | reading the file around the pattern, not the pattern's output | The sixth shape needs its own line because it is the only one that makes an entry read *better* than it is. §5.3's `sans` box said "nothing blocks it now @@ -3895,6 +4092,31 @@ as a one-row table beside the list rather than folded into it, which is verbatim the hazard the third pass warned about when it created the list. Both are in the table now. +**Twenty-third pass, 2026-08-15 — the seventh check, and it reads another repo's +source rather than waiting for that repo to emit something.** Reason 1 at the top +of this section says the fix for every other cross-repo seam here was making the +upstream repo *emit* data (`options-json`, `wm-bindings-json`, `ports.meta.json`), +and that this one stayed prose on both sides. `pounce-item-grammar` takes the +third door: **a flake input already IS the upstream repo, at the rev you ship**, +so `${pounce}/pkgs/pounce/ItemSettings.swift` is readable in a `runCommand` with +no cooperation from pounce at all. It diffs `modules/pounce/item-grammar.nix` +against `ItemTarget`'s mode list and its error text, and it is in the all-systems +set — so unlike `accent-reach` it fires on CI as well as on this Mac. What it +caught, retroactively, was a day-old drift: pounce#80 added `shortcut:`, +haus's lock moved to it two minutes later, and the layer went on refusing the +key (§5.9(b)) — the sharpest statement of reason 1 this section has: **the +automation that keeps the binary current runs in minutes, and the mirror of what +that binary understands moves at the speed of somebody noticing.** + +**Ten ★ findings, seven checks** (`data-only-surface`, `accent-reach`, `packs` +carrying two, `fragment-compat`, `scale-reach` carrying two, `font-reach` +carrying two, `pounce-item-grammar`), still one *warning* counted separately. +The candidate list keeps §5.6's and gains one from this pass, phrased so it can +be asked of anything: **which dimension of this copy did I decide was closed?** — +the pounce mirror reasoned carefully about the six mode names it copies and never +about the four prefixes it enumerates, which is the twenty-second pass's +one-option blindness with a mirror in place of a check. + --- ## 6. Phasing