Edits made to core files
The core of a site is a copy of a published build. cms9:self-update and
the nightly cms9:auto-update replace those files with the files of the next
build, so every edit made inside app4/app/, app4/themes/administrator/,
app4/themes/default/ or app4/resources/ is temporary by definition.
Until build 527 that happened without a word: the updater verified the downloaded files against the new manifest and never asked whether the files on disk still equalled the build they came from. A patch applied on a production site disappeared at the next update, and the only sign was the old behaviour coming back.
Now the update asks first. The yardstick is the manifest of the installed
build — one sha256 per file, exactly what the build server published — cached
in writable/update/core_baseline.json:
- files that differ, files added under the core roots and files deleted locally are found before anything on disk is touched;
- an apply that would overwrite an edit stops with
local_changesand lists the files; cms9:auto-updateskips only the core on such a site (modules keep updating) and raises a banner in the admin panel;--overwrite-localreplaces the edits on purpose and keeps a copy of every file it replaced.
The same idea as local changes in a module, applied one level up.
See what this site changed: cms9:core-drift
php spark cms9:core-drift # human-readable report
php spark cms9:core-drift --json # machine-readable
php spark cms9:core-drift --fetch # take the yardstick from the channel now
The command only reads, and without --fetch it makes no network call at all.
Run it per site — over an ssh loop or a deploy tool — before cutting a release;
nothing here aggregates several sites, because nothing here talks to them.
| code | meaning |
|---|---|
0 | every core file equals the build that shipped it |
1 | core files were edited on this site (listed by name) |
2 | no baseline for the installed build — no assessment is made |
7 | usage error |
Exit code 2 is an honest "unknown", not a failure: a site that has not
updated since this feature landed has no cached manifest yet. It gets one
automatically at the next core update, or immediately with --fetch — the
channel serves the manifest of any published build without a licence token.
What is never counted as an edit
A clean install must report zero, otherwise the whole gate becomes noise that
operators learn to pass with --overwrite-local. Excluded by design:
writable/(caches, logs, sessions, uploads),.env, the public docroot, the composer tree at the install root and everytests/directory;- editor and backup leftovers:
*.bak*,*.orig,*.rej,*~,*.swp; .baseline.jsonof core-shipped modules — the core update rewrites those itself on every site;- modules the core package never shipped (store and site modules such as Pixozip or CloudStorage);
- themes of the site: only
administratoranddefaulttravel in the core package, every other theme is the site's own; - a file that already equals the incoming build (the site applied the same
fix by hand), and a file this particular update does not touch at all: both
are reported by
cms9:core-driftbut neither blocks the update.
A file deleted locally is listed too, and never blocks: the update simply puts it back.
What the update does
php spark cms9:self-update # dry run: says it up front
php spark cms9:self-update --apply # refuses, lists the files
php spark cms9:self-update --apply --overwrite-local # replaces them on purpose
The check sits before the backup step, so a refusal changes nothing on
disk. With --overwrite-local the edited files are copied to
writable/core_local_changes/<release>-<timestamp>/ first, keeping the site's
version of every file the update overwrote, and the path is printed in the
log.
An unattended run (cms9:auto-update) never overwrites. It writes the skip and
the file names into writable/logs/auto-update.log, records the held build and
shows it in the admin panel — in the chrome banner and on System update.
The banner closes with «×» for the build it names and comes back for the next
one, so a site that keeps its edits is reminded once per build, not once per
page.
Carrying the edits somewhere they survive
The gate makes an edit visible, not permanent. Three supported places for the change itself:
1. The theme, when the change is presentation
Markup, CSS and JS of the storefront belong to the theme. A theme other than
default and administrator is not part of the core package and survives
every update.
2. A module of your own, listening to events
Business behaviour — an extra check at checkout, a field on an order, an
integration — belongs in app/Modules/<YourModule>/ with event handlers
instead of an edited core class. Modules are updated independently of the core.
3. A fix in the core itself, upstream
If the change belongs in the core — a real bug, a missing hook — it should be made in the core repository and reach the site as a build. That is the only form of "core edit" that survives, and it benefits every other site as well.
php spark cms9:core-drift --json on every site of a fleet answers "which
installs would this build break" in one pass — while the build can still grow
the hook that makes the edit unnecessary.