# User stories — derived from ckn-bw git history Date: 2026-05-10 History range examined: first commit `572e29e` (2021-06-11) … HEAD `c03b033` (2026-05-10), total 1169 commits. Detailed per-file analysis covers the last 18 months (222 commits, 2024-11 → 2026-05); subject-line scans extended back 36 months for context. The pre-2024 history is dominated by the same patterns at lower velocity, so deeper sampling there did not surface new stories. ## Methodology Three passes: 1. **Bulk extraction.** `git log --since="18 months ago" --name-status -M -C` written to `/tmp/ckn-bw-history.txt`. 1023 lines covering all 222 recent commits with file changes and rename/copy detection. This is the primary corpus. 2. **Frequency analysis.** Per-bundle / per-node / per-area churn counts; first-word commit-message frequency; A/M/D/R action breakdown (461 modifies, 95 adds, 22 deletes, 9 renames over the window); rename pairs extracted for naming-convention inference. 3. **Targeted spot reads.** `git show --stat` and full diffs on 10 commits representative of the high-stakes / cross-cutting / burst patterns (bw5 migration `186d503`, telegraf reactor refactor `4959461`, wol-waker scope-down `985a15e`, l4d burst seed `3469d98`, mailman introduction `d3b8e2e`, debian-13 `c41e6f8`, etc.) to verify intent matches message. Clustering rule: a story is a recurring **user intent**, not a recurring **file pattern**. Two commits both touching `metadata.py` may be different stories. Aimed for stories with ≥3 grounding commits each. Final count: 19 stories. The prior pass produced 12 stories. This pass adds 7 (investigation, trial-and-error microbursts, disable-for-now, cleanup-of-dead-code, subsystem deep-dive bursts, README touch-ups, naming-convention renames) and subdivides several of the original twelve. ## Story index Ordered by frequency, recent-window unless noted. 1. **S1 — Tune one bundle's metadata** (~50+ commits). Small targeted `metadata.py` edits. 2. **S2 — Tweak one node's config** (~80+ commits, top-heavy). Per-node tuning, often serial. 3. **S3 — Edit a file template** (~40+ commits). `bundles//files/*` changes. 4. **S4 — Trial-and-error microburst** (frequent within bursts; ~15 burst-clusters). Successive "fix"/"+"/"wip" commits. 5. **S5 — Subsystem deep-dive burst** (5 major bursts in 18 months: l4d2, routeros, routeros-monitoring, bootshorn, debian-13). 6. **S6 — Modify items.py for a bundle** (~30 commits). Item logic / dep tweaks. 7. **S7 — Templated data update** (~15 commits). `data/apt/keys/*`, `data/grafana/rows/*`. 8. **S8 — Add a new bundle** (10 in 18 months: yourls, mailman, routeros, routeros-monitoring, pppoe, bootshorn, kea-dhcpd, proxmox-ve, ifupdown, network). 9. **S9 — Onboard a new node** (10 nodes added in 18 months). 10. **S10 — Group adjustment / new OS variant** (~12 commits). `groups/os/*`, `groups/locations/*`. 11. **S11 — Disable temporarily / "for now"** (~10 commits). Commenting out, dummy-ifying. 12. **S12 — Cross-bundle metadata-reactor refactor** (4 high-impact commits in 18 months). 13. **S13 — Cleanup / delete obsolete code** (~8 commits). `_old` bundles, removed nodes. 14. **S14 — Add or revise an operator script** (`bin/`) (~8 commits, 4 new scripts in 18 months). 15. **S15 — OS-version upgrade campaign** (3 campaigns: debian-12, debian-13, trixie/server-only). 16. **S16 — Add/modify a lib** (`libs/`) (5 commits in 18 months; 1 new lib). 17. **S17 — Add/modify a hook** (`hooks/`) (4 commits in 18 months; 2 new hooks). 18. **S18 — Per-bundle README touch-up** (~8 commits, sporadic). 19. **S19 — Naming-convention rename** (4 renames in 18 months). 20. **S20 — Bundlewrap version migration** (1 commit, but uniquely high-stakes — counted because it shapes the maintainer's roadmap). 21. **S21 — Interactive `bw debug` investigation** (.bw_debug_history evidence). (Numbering shows 21 because three are kept short — S20 is a single high-stakes commit, S17 / S21 are low-frequency but materially distinct workflows. Anything that did not reach ≥3 grounding events was rolled into cross-cutting findings.) ## Stories ### Story 1: Tune one bundle's metadata **Pattern.** Open `bundles//metadata.py`, change a value or add a key, commit. The most common single-file edit. Often very small (`fix typo`, `relax telegraf collection`, `mailserver zfs params`). The change is *intra-bundle* — no reactor surprise; just adjusting a default the bundle itself reads. **Frequency.** ~50 commits over 18 months across many bundles. Recent: `7f20c94` (telegraf deprecations, 2026-03-09), `a7c7aaf` (nc preview options, 2026-03-09), `5fab21b` (apt install ca-certificates, 2026-02-10), `0d35bc2` (linux relax icmp ratelimit, 2026-02-10), `5fb1ee5` (less annoying root passwords / users, 2025-08-09). **Files typically touched.** Single file: `bundles//metadata.py`. Sometimes one paired sibling file when the new metadata has an items consequence (see Story 6 for pure items work). **Variations.** - (a) **Tune an existing value.** Change a number, add a member to a set, swap a string. ~70% of cases. - (b) **Add a new key.** `'unattended-upgrades': {}` added to the apt packages dict in `186d503`; `apt install ca-certificates` in `5fab21b`. - (c) **Restructure a sub-tree for new behavior.** Less common; tends to bleed into Story 12 if it changes a reactor's contract. **Evidence.** - `7f20c94` (2026-03-09) — telegraf deprecations (2 files: bundles/routeros-monitoring/metadata.py + bundles/telegraf/items.py). - `0d35bc2` (2026-02-10) — `linux relax icmp ratelimit` (1 file). - `5fab21b` (2026-02-10) — `apt install ca-certificates` (1 file). - `a0f5f80` (2026-01-11) — typo fix in routeros-monitoring metadata. - `bdb9fa0` (2025-06-22) — `gitea disable registration` (changes `bundles/gitea/files/app.ini` template — boundary case with Story 3). **Co-modification.** Usually none — single file. Occasionally pulls in `items.py` if the new key changes which file/service item is rendered. Almost never touches nodes (the value sits in defaults, nodes don't need to opt in unless a top-level key didn't exist before). **Stakes.** Routine when the bundle's metadata is read only by itself. Moderate when the bundle uses cross-bundle reactor writes (e.g. telegraf, monitored). The maintainer's mental model assumes "metadata.py change → only the bundles using this bundle are affected"; that's only true if the bundle keeps to its own namespace. See Story 12 for the cross-bundle case. **Implications for agent docs.** Well-served by `bundles/AGENTS.md` plus per-bundle `AGENTS.md` (PR2). Agents need to know: - The bundle's own resolved metadata shape — covered by per-bundle template's `Metadata` section. - Whether *this* bundle writes into other namespaces (cross-bundle ripple) — must be in the per-bundle `Gotchas` for any bundle that does (telegraf, monitored, wol-waker, archive). `commands.md`'s after-change row already covers it generally, but a per-bundle callout shortens the agent's loop. - After-change verification: `bw hash ` — the table in `commands.md` covers this. **No gap.** ### Story 2: Tweak one node's config **Pattern.** Edit a single `nodes/.py` to change something node-specific: an interface, a hostname, a group membership, a metadata override. The most common workflow, but heavily skewed to a few "hot" nodes — `home.router.py` (25 touches/18mo), `ovh.secondary.py` (29), `home.server.py` (16). Most edits are tiny. **Frequency.** ~80 node touches in 18 months across 21 nodes. 70% concentrated in 5 nodes. **Files typically touched.** Single node file. Occasionally two when the change is "move responsibility from node A to node B" (e.g. `5a8dc7e` 2025-12-03 moves wakeonlan to its own vlan, edits home.backups.py + home.router.py + home.switch-rack-poe.py + groups/os/routeros.py). **Variations.** - (a) **Per-application config tweak** (e.g. ovh.secondary l4d server config — see Story 5). - (b) **Hardware/network shape change** (home.server "more swap"; "fan/motherboard sensors"; home.router "fix dhcp"; "fix ip"). - (c) **Group membership change** — adding/removing strings in the node's `groups` list. Cross-cuts with Story 10. - (d) **Bundle attachment** — adding a bundle to the node's `bundles` list (rare; usually goes via group instead). **Evidence.** - `838da64` (2026-03-09) — home server fan/motherboard sensors (1 file). - `fcd92db` (2026-03-09) — more swap (1 file, home.server). - `4652a42` (2026-01-10) — disable zfs mirror for now (home.backups.py — also a Story 11 example). - `60f29aa` (2025-08-24) — fix hue dhcp (home.hue). - `0e78afe` (2024-11-23) — fix ip (home.router). **Co-modification.** Often none. When the change is a group-membership shift, the receiving group's file (`groups/os/routeros.py`, `groups/locations/home.py`) gets a paired edit. Network-shape edits frequently pull in 2-3 nodes plus `bundles/network/metadata.py`. **Stakes.** Routine for value tweaks; moderate when the node provides services others depend on (home.router, htz.mails). High if node-file syntax is broken — recall that `nodes/.py` is `eval()`'d, so a syntax error or top-level `import` breaks the loader for all nodes (commit `dc40295` in 2025-06-22 added an explicit `print message on parsing group error` to `groups.py`, suggesting the user has been bitten by silent loader failures). **Implications for agent docs.** `nodes/AGENTS.md` should call out: - The `eval()` constraint (no top-level imports, only expression-level constructs) — already in spec §5. - Where to find resolved metadata after the edit (`bw metadata `) — covered by `commands.md` row "nodes/.py". - Naming convention `..py` and how it's tied to the loader — covered. - **Gap:** the `eval()` failure mode is silent; the user himself patched the loader to print a message. Worth an explicit *pitfall* in `nodes/AGENTS.md`: "if `bw nodes` reports nothing or fewer nodes than expected, you have an `eval()` error in a node file." Otherwise an agent will assume the file was loaded. ### Story 3: Edit a file template **Pattern.** Open `bundles//files/