ADR-0020 — init defaults to the home scaffold, shipped as package data¶
- Status: Accepted (2026-07-13)
- Date: 2026-07-13
- Relates to: ADR-0003 (generic onboarding —
this ADR flips its default surface and fixes the packaging of the scaffold set) ·
ADR-0019 (the
edge.tomlthe scaffold emits) ·anvil_serving/init.py,anvil_serving/_scaffold_templates/,pyproject.toml,scripts/sync_scaffold_templates.py
Context¶
PR #252 added init --home: a full operational-config scaffold (all serves*.toml, compose files,
operator-topology.toml, the voice manifest, .env.example, and the ADR-0019 edge.toml) written
into the config home so a fresh machine runs serves up --group NAME with zero hand-assembly. It
shipped with two defects:
-
Packaging — the installed tool is broken.
init --homeresolved its reference files via__file__/../examples(_EXAMPLES_DIR). That path only exists in a source checkout; theexamples/tree is not inside theanvil_servingpackage and is not shipped in the wheel (pyproject.tomlpackage-datanever included it). Running the console-script from a normalpip/uv tool installtherefore failed with "the shipped reference examples are not available next to this install." The feature worked only for developers running from a git checkout. -
Wrong default (operator directive). Bare
initstill did the single-model, single-file bring-up into the CWD, and the full operational scaffold — the thing an operator standing up a machine actually wants — was hidden behind--home. The operator asked for the reverse.
The build backend is setuptools (pyproject.toml [build-system]). Setuptools can only ship
data resolvable via importlib.resources when it lives inside a package directory; files under
the repo-root examples/ tree cannot be included as importable package resources without moving or
mirroring them.
Considered options¶
data_files/MANIFEST.intheexamples/tree.MANIFEST.inonly affects the sdist, not the wheel;data_filesinstalls outside the package and is not addressable viaimportlib.resources.files(anvil_serving). Rejected: neither makes the set resolvable from an installed console-script the way the code needs.- Move
examples/primary-nodeinto the package. Rejected:examples/is a real, browsable reference instance with far more than the scaffold subset; relocating it churns docs and the CLI audit inventory for no benefit. - Mirror the needed subset into a package data dir, resolve via
importlib.resources, and guard the mirror against drift with a test + sync script. Chosen. The canonical copies stay inexamples/; the wheel ships a byte-identical mirror it can actually find.
Decision¶
- Ship the scaffold set as package data. The subset
initneeds is mirrored intoanvil_serving/_scaffold_templates/and declared inpyproject.toml([tool.setuptools.package-data]→"anvil_serving" = ["_scaffold_templates/*"]).initresolves every template viaimportlib.resources.files("anvil_serving._scaffold_templates")— never__file__/../examples. This works identically from a wheel install and a source checkout. - No drift.
examples/primary-node/+examples/voice/remain the canonical copies.scripts/sync_scaffold_templates.pyregenerates the mirror;tests/test_init.py(test_scaffold_templates_match_examples) fails the suite if the mirror ever diverges, and a packaged-path test resolves the set the way an installed tool does so the #252 regression cannot return. - Flip the default. Bare
anvil-serving initnow scaffolds the full operational config home (default target~/.anvil-serving, honoringANVIL_SERVING_HOME; override with--out-dir). The single-model CWD bring-up moves behind--single-model.--homeremains as a hidden, deprecated alias for one release (prints a deprecation note), then is removed. - Safety preserved. No-overwrite-without-numbered-backup and placeholder-only (no secrets, no real GPU UUIDs or tailnet IP) behavior are unchanged for both paths.
Consequences¶
- Breaking change (pre-1.0, operator-requested). Anyone scripting
anvil-serving initfor the old single-file CWD bring-up must add--single-model. Recorded inCHANGELOG.mdunder Unreleased. - The installed tool works.
uv tool install/pip installof anvil-serving can runinitfrom any cwd and get the full set with no "examples not available" error. - New maintenance obligation: when a mirrored source file under
examples/changes, runscripts/sync_scaffold_templates.py(the drift test will otherwise red the suite). This is the deliberate cost of setuptools not shipping out-of-package files as importable resources. - ADR-0003's substance (portable defaults, a generated single-model bring-up) is unchanged — only its
default command surface moves behind
--single-model.