feat: build system hardening — collision detection, validation gates, init path enforcement

5-phase hardening to prevent silent file-layer collisions (the D-Bus
regression class):

Phase 1: lint-config-paths.sh + make lint-config in depends.mk
Phase 2: CollisionTracker in installer (content-hash comparison)
Phase 3: installs manifests in recipe.toml + validate-file-ownership.sh
Phase 4: validate-init-services.sh + make validate in disk.mk
Phase 5: documentation (AGENTS.md, BUILD-SYSTEM-HARDENING-PLAN.md)

Both redbear-mini and redbear-full build and validate clean.
66 declared install paths in base, zero conflicts.
This commit is contained in:
2026-05-03 22:25:22 +01:00
parent 907d447369
commit 2e764746e7
21 changed files with 1503 additions and 69 deletions
+52
View File
@@ -382,3 +382,55 @@ Based on these invariants, the first practical implementation slice should do al
4. ensure release-mode source trees before deep build execution
If those four changes land cleanly, the build system will already move from reactive deep-build debugging toward proactive build-state validation.
---
## 6. Installer file-layer separation
### Layer ordering
The installer (`install_dir()`) processes files in this order:
1. Config pre-install `[[files]]` (`postinstall = false`)
2. Package staging (`install_packages()`)
3. Config post-install `[[files]]` (`postinstall = true`)
4. User/group creation (`passwd`, `shadow`, `group`)
Layer 2 silently overwrites Layer 1 files at the same path. Layer 3 overwrites
Layer 2. There is no collision detection or warning.
### Invariant I1: Init service path separation
Config `[[files]]` entries that create or override init service files MUST use
`/etc/init.d/` paths. Package-owned service files go in `/usr/lib/init.d/`.
The init system's `config_for_dirs(["/usr/lib/init.d", "/etc/init.d"])` uses a
BTreeMap keyed by filename. For the same filename, the `/etc/init.d/` entry
overwrites the `/usr/lib/init.d/` entry, so config overrides take effect.
Config entries using `/usr/lib/init.d/` paths will be silently overwritten by
package staging. The `scripts/lint-config-paths.sh` tool detects violations.
### Invariant I2: Config override survival
Any file created by config `[[files]]` that must survive package installation
MUST use a path that packages do not install to.
For init services, `/etc/init.d/` provides this via the `config_for_dirs()`
BTreeMap mechanism. For other file types, either use `/etc/` paths or mark the
file as `postinstall = true`.
### Invariant I3: Post-install as explicit override
`[[files]]` entries with `postinstall = true` run after package installation and
are guaranteed to overwrite any package-provided file at the same path.
Prefer `/etc/` directory overrides over `postinstall` for init services, because
`postinstall` requires every override to be explicitly marked and is easy to
miss. The `/etc/init.d/` path convention is self-enforcing via `config_for_dirs()`.
### Enforcement
- `scripts/lint-config-paths.sh` — detects `/usr/lib/init.d/` paths in config files
- `make lint-config` — runs the lint as a build step
- Full plan: `local/docs/BUILD-SYSTEM-HARDENING-PLAN.md`