feat(install): add OverlaySystem — the missing Update.md step-3 tool, so an update can refresh system-owned files - #1771
Conversation
copyMissing writes only when the destination is absent, and it is the only mechanism the deploy tools use, so an update can add new files but never refresh a changed one. Measured on a documented 7.1.1 -> 7.28.3 update: 31/50 hooks, 61/143 tools, 44/51 docs and the whole Algorithm stayed at the old version while VERSION could be bumped to the new one. OverlaySystem overlays only system-owned paths, never deletes, never touches USER/, LIFEOS/MEMORY/ or settings.json, and writes VERSION last and only on a fully successful apply. Refs danielmiessler#1770
|
Thanks @bnkath2o — and to Abe for the writeup. This is the fix for the single most-reported problem in the repo: Ported into the source tree as One known gap I left for a follow-up: Closing here since the public repo is generated from the source. Ships in the next release. |
This is Abe, Ben's AI Assistant, contributing on Ben's behalf.
Implements
Workflows/Update.mdstep 3, which describes a re-overlay of the system templates but has no tool behind it.Fixes #1770 · methodology behind the validation: discussion #1769
Why
InstallEngine.copyMissingwrites a file only when the destination is absent (InstallEngine.ts:364), and it is the only mechanism the deploy tools use. That is the correct contract for a fresh install and means an update can add new files but never refresh a changed one.Measured on a 7.1.1 → 7.28.3 update performed exactly as documented, before this change: 31 of 50 hooks, 61 of 143 tools, 44 of 51 docs and the whole Algorithm (
LATEST8.4.0 vs payload 8.17.3) were left at the old version, whileLIFEOS/VERSIONcould be bumped to the new one. BecauseStopGates,PreToolGuard,PostToolObserverandMemoryTurnStartwere among the stale files, the new ISA close gates and the ⚙️ SYSTEM line could not fire even though the constitution mandating them installed fine. Full detail in #1770.What this adds
Tools/OverlaySystem.ts— followsDeployCore's shape: dry-run by default,--applyto mutate,--config-root/--skill-root, refuses a dev tree with exit 2, JSON result,process.exit(ok ? 0 : 1).Scope is a fixed allowlist of system-owned paths:
Never touched:
USER/,LIFEOS/MEMORY/,settings.json(InstallHooks owns that merge),node_modules,.git,out,.next. Symlinks are skipped rather than followed or replaced, so theUSERsymlink contract is safe.Nothing is ever deleted. A file is written only where the payload ships one at the same relative path, so operator-authored hooks, skills and tools survive untouched.
LIFEOS/VERSIONis written last, and only when every copy succeeded — so a partial update cannot leave a marker claiming the new version.Workflows/Update.mdstep 3 updated to call it.Validation
Rolled a VM snapshot back to the pre-overlay state and re-ran the entire update with the tool in place:
VERSIONleft at 7.1.1--applyVERSION7.1.1 → 7.28.3--applyupdated=0, created=0, alreadyCurrent=1556— idempotentLATEST8.4.0 → 8.17.3, spec file presentUSER/LIFEOS/MEMORY//healthz/work/telos200One ordering note
Because the tool restores
CLAUDE.mdfrom the template, the identity imports return to their commented state, soActivateImports.ts --applymust run after it. That is alreadyUpdate.mdstep 6, so the documented order is correct as written — worth stating explicitly since the failure would be silent, andActivateImportsreturnsok: trueeven when it activates nothing (#1728). Verifying by readingCLAUDE.mdback is the reliable check.Not included
No change to
copyMissingorDeployCore— their additive contract is right for fresh installs and other callers depend on it. This is a separate tool for the update path.Happy to adjust scope, naming, or the allowlist to taste.