bundlewrap/groups/AGENTS.md
CroneKorkN 04558a9189
docs: scaffold agent-friendly entry points (Phase 1)
introduces a balanced set of agent + human docs:

- root AGENTS.md (with CLAUDE.md symlink) — 5-rule quickstart,
  layout map, mental model, use-case keyed example pointers.
- docs/agents/conventions.md — vault/demagify, eval-loader
  constraints, group inheritance, naming, do-not-touch list,
  suspension idioms, working-style notes.
- docs/agents/commands.md — repo-specific deltas to the fork's
  bw runbook (apt-key offline-verify, *.py_ suspended-node
  visibility, vault-echo rule).
- per-area AGENTS.md for bundles/, nodes/, groups/, libs/,
  hooks/, data/, items/, bin/ — mechanism-focused, no enumeration.
- bundles/AGENTS.template.md — per-bundle doc template with
  optional `## Writes into` section for cross-namespace reactors.

bundlewrap-language reference (item types, dep keywords, reactors,
runbook, three-tier safety envelope) is not duplicated here; we
link out to the fork's AGENTS.md instead.

bw test still green. all internal links resolve. Phase 0 invariants
preserved (libs/hooks docstrings, bin/* # purpose: headers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:44:45 +02:00

4 KiB

groups/

What's here

Groups attach bundles and shared metadata to nodes. One file per group, organized by axis:

groups/
├── all.py                     # universal base (every node belongs)
├── applications/<x>.py        # role-shaped groups (mailserver, monitored, …)
├── locations/<x>.py           # physical/network location (home, htz, …)
├── machine/<x>.py             # hardware kind (hardware, hetzner-cloud, raspberry-pi)
└── os/<x>.py                  # OS major/variant (debian-13, debian-13-pve, routeros, …)

Loader mechanism

groups.py (top of the repo) walks groups/ and runs eval() on each *.py. Same eval-as-expression rule as nodes/: one dict literal, no top-level imports, no statements. Errors print and the group is skipped — a real foot-gun, since a missing group silently changes node membership.

Group files are not demagified. Magic strings like !password_for:<x> only resolve in nodes/*.py. Inside a group file, call repo.vault.<verb>(...) directly.

Inheritance and merge order

Metadata merges along this chain:

all → location → os → machine → applications → node

Per-axis subdirs are conventional, not enforced — bw doesn't read the subdir. Each group lists its supergroups, and bw resolves the DAG. Membership is set-union; metadata merge follows the order above, with the node's own metadata block winning last.

Conventions

  • One group per file. Filename without .py = group name. Subdir groups them by axis for humans, not for bw.
  • Family files for OS variants. Common parent + per-variant child. Example: debian-13-common.py is shared by debian-13.py and debian-13-pve.py. Use this pattern when introducing related-but-distinct OS group families.
  • all.py is the universal default. Currently empty ({}); kept for the rare repo-wide opt-in.

How to add a group

  1. Pick the right axis subdir (or root all.py for universal default).

  2. Create groups/<axis>/<name>.py as a single dict expression:

    {
        'supergroups': [
            # parent groups whose bundles/metadata this one extends
        ],
        'bundles': [
            # bundles every member of this group should have
        ],
        'metadata': {
            # shared metadata for members
        },
    }
    
  3. Wire the group into the relevant nodes/*.py ('groups': {...}) or groups/*.py supergroups list.

  4. Verify with bw nodes <node> -a groups and bw metadata <node>.

How to add a new OS major (recipe)

Pattern from prior debian-12 → debian-13 work:

  1. Add groups/os/debian-N.py and groups/os/debian-N-common.py parallel to the existing files. Don't edit in place.
  2. Add data/apt/keys/debian-N-*.{asc,gpg} for the new release's signing keys. See commands.md#apt-key-changes-need-offline-verification before pushing keys live.
  3. Bump dependent bundles that branch on os_version / os_codename (bundles/bind/items.py, etc.).
  4. Bump affected nodes' groups lists one at a time. Apply, watch.
  5. Delete the old OS group file once no node references it.

Pitfalls

  • bw groups -n <node> doesn't exist. Use bw nodes <node> -a groups.
  • Cycles. A group can't be its own supergroup transitively; bw test catches this but the error message is terse.
  • Silent eval failure. A group file with a syntax error is skipped and prints a one-line error. If a node loses bundles unexpectedly, scan groups.py output for the error.

See also