Официальный сайт SLAED CMS
Журнал изменений
Step 76, batch 0 of docs/11-UPDATE-2026.md: the two open questions are answered by the owner after probes on the stand and the console, and the baseline of the suite is recorded. A site without sodium or zip still receives every release, and a release no longer overwrites the sitemap and robots.txt a site writes for itself.
Core changes:
- Decisions (docs/11-UPDATE-2026.md):
Without sodium the core verifies the signature with a bundled paragonie/sodium_compat, loaded only when the extension is missing; signing stays on real sodium
Without zip the archive is read by PharData; Update walks the central directory itself and applies every refusal to the raw names, the same for both readers
- PharData drops ../x and /x silently, loses the symlink mode and throws on a backslash, so its names prove nothing
The nginx access log of slaed.net goes without the query string for /api/v1/master/, so the privacy note keeps its promise
- public/sitemap.xml leaves git; public/robots.txt is a site file in files.json, placed only when missing
- The whole tree ships; nothing beyond the named export-ignore entries leaves the archive
- Plan sections and steps (docs/11-UPDATE-2026.md):
- The client, the refusals, the privacy, the help and the tests follow the decisions; steps 3, 6 and 10 carry them
"Open questions" becomes "Probes of step 0": stand extensions and opcache, write rights and one device, the speed of sodium_compat, PharData on the tree archive and on hostile entries, the archive size, the suite baseline
- Roadmap (docs/ROADMAP-2026.md):
- Step 76 is closed with its decisions
Benefits:
- A site on a host without sodium or zip is not left blind to updates forever
- One set of refusals holds whichever reader opens the archive
Technical notes:
- Plans only; no code, theme or schema change
- Baseline: full phpunit green, 1849 tests, 24477 assertions, 14 skipped
Three audits of docs/11-UPDATE-2026.md were checked against the code and folded in. The site fetches releases only from slaed.net, the installer comes from the verified new package while the installed rescue keeps the way back, and the parts of 8.0.0 that no later release can fix are frozen as contract version 1.
Core changes:
- Sources, signature and release (docs/11-UPDATE-2026.md):
The client fetches only from slaed.net, with no redirect; GitHub Releases stay for people, and a GitHub mirror can come in 8.x if the traffic of slaed.net demands it
- the manifest carries no URL; versions are strictly X.Y.Z, the code name is the constant SLAED_CODENAME
Order of a release: build, sign, rehearse, publish; the tag stays local until publish
- publish refuses test factory keys and an archive without a green rehearsal of its hash
- build refuses an archive grown by more than 20 % until --max confirms it
- the delta compares two files.json lists; the first release after 8.0 is an empty 8.0.1
- A pause withdraws the offer: the found version is reset and a manual upload needs the manifest the master serves now
- Installation and rollback (docs/11-UPDATE-2026.md):
Contract version 1 frozen in 8.0.0: the apply.lock schema, the backup layout, the entry of the portioned dump, and the call of the rescue by the entry guard
- The executor run/apply.php is copied from the verified new package; core/rescue.php is replaced only at step 8
All three entry points answer 503 before the core while apply.lock exists or the schema lags the code; the rescue form goes straight to the installed rescue
The new Backup mode dumps in portions without InnoDB and only the table list of the site fixed at preflight; the restore drops only tables created by the failed install
Database steps run before the config merge in the light boot UPDATE_FILE, with its own admin login; the manual path goes through it on a closed site
Every rollback returns files, config/ with the step marks and the database, then clears opcache, config/local.php and the page and asset caches
- The config merge is recursive with lists kept whole; config/ files stay out of the integrity check and deletion
- Preflight checks opcache reset rights and the database privileges of the dump; a self-check and a dry run follow
- Master (docs/11-UPDATE-2026.md):
- master/file is read-only; the rollout cohort is crc32 of sid and version; the flood guard counts IPv6 per /64
- The scheduler task masterwatch pauses a release by its failure rate and mails the owner
- Help in the panel (docs/11-UPDATE-2026.md):
admin/info/update/ru.md and modules/master/admin/info/ru.md are written by the step that builds each part; the help of the scheduler, languages and templates follows; a step is not done without its help
- Roadmap (docs/ROADMAP-2026.md, docs/2027-ADMIN-UX.md):
- The update plan is batches 0-11 at steps 76-87; the database batch is split from the installation batch
- DIAL moves to step 88 after the updater; Close stays last at 89
Benefits:
- A fault of the installer is fixed by the next release instead of being frozen into every site
- No path of the plan runs new code on an old schema or overwrites tables of another installation
Technical notes:
- Plans only; no code, theme or schema change
A site learns of an update only by code it already has, so the updater is planned before the release: docs/11-UPDATE-2026.md designs signed releases, the module master on slaed.net and the core page "Обновления", and roadmap steps 77-87 carry it. The admin stand shares the sound engine and the Phoenix scene with the labs.
Core changes:
- Update plan (docs/11-UPDATE-2026.md, docs/ROADMAP-2026.md):
The owner's decisions of 2026-10-15: Ed25519 offline signature, primary and recovery keys, GitHub Releases first and slaed.net second, a full archive and a delta, installation in steps with rollback and rescue
- the master is the module master, kept in the repository and out of the release archive
- settings get missing options added, languages and the themes admin and lite are replaced whole
- Roadmap steps 77-87 are the batches 0-10 of the plan; Close moves to step 88
- Connector and entry (docs/2027-CONNECTOR.md, docs/2027-VINE-MONITOR.md, docs/ARCHITECTURE.md):
- The API is the branch /api/ of public/index.php instead of an own public/api.php
- Steps 1-2 of the connector and the service update move into the update plan; the connector starts at step 3
- Ideas after the release (docs/2027-IDEAS.md):
- Numbered raw ideas: automatic security fixes, a public "SLAED in numbers" page, error reports by consent
- Admin stand (public/demo/admin/*, public/demo/assets/sound.js, public/demo/assets/phoenix-scene.js):
- The sound engine window.slSound leaves the sound lab for a shared file; the admin stand plays its sounds
The Phoenix scene and its rest timer window.slPhoenix move into their own file; the admin stand runs the screensaver after a rest
- public/demo/phoenix.html, public/demo/snd-01-lab.html and public/demo/assets/phoenix.js load the shared files
Benefits:
- Every 8.0 site can receive 8.0.1 without its owner visiting slaed.net
- One sound engine and one Phoenix scene for every stand page
Technical notes:
- Plans and stand pages only; no core, theme or schema change
The Phoenix stand had fifteen separate faces of the logo; three of them are merged into one screensaver of sixty seconds that starts after a rest of the hand, runs in a circle and fades out when the hand comes back. A second stand page lets the owner hear and choose the sounds of messages, mentions, alerts, wins and moderation before a standard of sound is written.
Core changes:
- Screensaver (public/demo/assets/phoenix.js):
One scene on its own clock in acts: the page dims to 75% while the code of the system rains in, sparks rise from the code into a giant A, the A falls to ash, the ash takes fire into the big logo whose letters grow around the A, the logo falls to ash and flies pixel by pixel into the header, falls again and becomes the real logo
- one particle for the whole scene, a square of smouldering ash; colours change by ramps, dots sit on whole
screen pixels, the rain is made of lines of core/system.php
- the quiet: ash rises from the foot of the page and every six seconds the beam of the installer crosses the
letters of the logo, sparks leave the A and the version badge flashes
- Chance comes from a seed and the ash moves in fixed steps, so any second can be reached by seeking
Screensaver rules: it starts after the chosen rest on the page (60 s by default), loops while nobody wakes it, a move, a key, a wheel or a scroll fades it out in 0.8 s; the quiet stays on as the ground of the page
- the measures of a round are taken when it starts, the beam follows the logo where it is now
- prefers-reduced-motion and the Motion option skip the scene and show the end state
- The fourteen other faces and their machinery are removed
- Stand player (public/demo/phoenix.html):
The dock is a player: play and pause, a stopwatch, a scrubber, chapters per act, speed 1/4, 1/2 and 1, the rest before the screensaver (10 s, 60 s, 5 min, off) and Launch (R)
- its clicks and keys neither wake nor count as the hand on the page; a paused player is left alone
- Styles (public/demo/assets/phoenix-fx.css):
Only what the screensaver uses: the ground over and under the page, the dimming veil next to it, the badge that springs out and shines with every beam
- Sound lab (public/demo/snd-01-lab.html):
Zones with variants: personal messages in and out, mentions and replies, alerts in their five theme tones (error, warn, success, info, accent, each with a silent choice), wins, the moderation queue
- synthesised voices in D minor, the key of the Phoenix theme, and three cuts of its strongest moments
Rules shown live: a sound always with a counter and the number in the tab title, one sound per category in 2.5 s, quiet on an open tab for what comes from others, full for the answer to one's own action, only alerts that appear sound, the silent switch of the phone where Safari allows it
- A master switch in sl.sound (1 or 0) next to sl.motion; zones, variants and volume in sl.sound.set
Benefits:
- One screensaver that tells the return of the system and gets out of the way the moment it is not wanted
- The sounds are chosen by ear on a working page before any of them goes into the theme
Technical notes:
- Stand pages only (public/demo/); no theme, core or schema change
- Music in the screensaver was weighed and left out: it is the default effect of the system
Version 1.0.4 of the presentation package. The headings, the texts and the captions take the brand face Magistral instead of Segoe UI and the monospace face, which stays only where code is shown, and the six videos are rendered again from the browser frames with the new face.
Core changes:
- Face (public/phoenix/vendor/base.css, public/phoenix/player/styles.css, public/phoenix/index.html):
- base.css gets the token --sl-face-body, Magistral with Arial and sans-serif behind it
The stage, the header, the kicker, the archive captions, the continuity row, the editor head, the build bar, the modules, the technology row, the HUD, the history and the controls use --sl-face-body
- the code of the editors and the continuity listings keep --sl-face-mono
- index.html stamps base.css ?v=2 and styles.css ?v=4
- Technology row (public/phoenix/index.html):
- Every item of the final row carries its Bootstrap icon: release, PHP, MariaDB, core, light and dark
- Videos (public/phoenix/exports/*.mp4, public/phoenix/presets/production/video-fullhd.json):
- The dark and light videos in 1080x1920, 1920x1080 and 720p are rendered again with the new face
- The production preset records the faces and their WOFF2 files
- Package (public/phoenix/manifest.json, public/phoenix/checksums.json, public/phoenix/README.md):
- Version 1.0.4 of 2026-10-14; checksums of the changed files
README says the texts are set in Magistral, the marks use Bootstrap Icons, and both WOFF2 files match the originals of the lite theme byte for byte
Benefits:
- The presentation speaks in the face of the brand, in the player and in every video
Technical notes:
- The WOFF2 files were already in the package; nothing new is loaded
- No change outside public/phoenix/
Readable addresses are the only form of a page address since 2026-10-14. The README and the help of the settings now state the one thing a server must do for them, so an installation on a server without the rule does not end with a start page that opens and every other page answering 404.
Core changes:
- Requirements (README.md):
Web Server names Apache or LiteSpeed with mod_rewrite and nginx with the server block of nginx.conf.example
- another PHP-capable server (IIS, Caddy) needs its own rule: every path that is no file or folder goes to index.php
The server section adds what happens without that rule: the start page opens, every other page gets the 404 of the server
- Help of the settings (admin/info/config/ru.md):
Page addresses and canonical: readable addresses are the only form and cannot be turned off; the separators / and - are fixed
- the server rule, public/.htaccess for Apache and LiteSpeed, try_files of nginx.conf.example for nginx
Benefits:
- An administrator learns the server requirement before the installation, not from a site that answers 404
Technical notes:
- Text only; no code, template or schema change
Batch 8 of docs/10-URL-2026.md (roadmap step 75): what lasts of the plan moves into the reference, the help of the panel and of the modules, and the plan is deleted. A live check of every address and every printed link on Apache (root on public/ and on the project) and on nginx found a redirect loop, a served dot file and a hard-coded link; all three are fixed.
Core changes:
Reference (docs/ARCHITECTURE.md, docs/NODE.md, docs/TEMPLATES.md, docs/VERSIONS.md, docs/TESTS.md, README.md, UPGRADING.md):
docs/ARCHITECTURE.md gets "Address Boundary": the address table, the registry _routes and the class Route, the name, when an entity gets and changes its path, migration, parsing, building and the one redirect
- the bootstrap, the routing keys, the front controller and "What Not To Assume" follow it
docs/NODE.md: the permanent type name, Route::checkTypeName(), "Addresses" of a type, the paths of the writers, page in place of num, the 301 of setSeoMove(), the canonical and href on paths
- the operation attach, which getNodeOps() no longer has, leaves "Public routes"
- docs/TEMPLATES.md: {{ root }}, the key route of setHead() and "Links In Templates"
docs/VERSIONS.md records the addresses of 2026-10-14 with their breaking changes; UPGRADING.md gets "Readable Addresses"; README.md the routing and the cut of the path; docs/TESTS.md the five Route tests
- Help of the panel and the modules (admin/info//ru.md, modules//admin/info/ru.md):
- config: page addresses and canonical, 301 and 410, where forcessl and forcehost act
- security: dot files refused, DirectorySlash Off in the project mode, the front controller required
- editor, scheduler: the front-controller rule must stay, the sitemap task writes the Sitemap line
- privat, search: /account/privat/123 and /search; template: {{ root }}
categories, node, voting: the field Address; account: a new login moves the profile address, the OAuth redirect URI keeps its query form on purpose
- Servers (.htaccess, public/.htaccess, nginx.conf.example):
The project .htaccess turns off DirectorySlash, which sent /docs to the folder docs/ of the project and back to /docs in an endless loop when the document root is the project
public/.htaccess and nginx.conf.example refuse every dot file or folder but .well-known/; nginx served /.htaccess with 200
- Theme (public/templates/lite/partials/site-footer.html):
- The ideas button links the path of its forum section instead of forum?cat=95, which answered 301
Plan closed (docs/10-URL-2026.md, docs/ROADMAP-2026.md, tests/Unit/Route*Test.php, tests/Support/registry_probe.php, docs/2027-CONNECTOR.md, docs/2027-VINE-MONITOR.md):
- The plan file is deleted, step 75 ticked; the tests and the 2027 plans point at "Address Boundary"
- Connector plan (docs/2027-CONNECTOR.md):
Full, safe administrator access for AI agents over MCP (owner, 2026-10-14): off by default, a risk class per action with an owner-set policy, short-lived keys, a fixed list of actions no agent gets, an audit trail
- api_client gains aid and policy, the table api_confirm holds confirmations, config/api.php the key risk
- a confirm policy answers input_required; a user key stays draft-only
Benefits:
- The reference and the help describe the addresses as they work, with nothing left in a plan
- Apache with either document root and nginx give the same answer to every address
Technical notes:
Live check: route matrix 57/57 and about 890 printed links on all three configurations, no relative address, no link answering 301, no missing-route warning in the log
- The stand's nginx file .osp/nginx/slaed.loc.conf (outside the repository) got the same dot-file refusal
Breaking: a dot file in public/ is no longer served; a server whose document root is the project needs DirectorySlash Off, which the shipped .htaccess carries
- No code or schema change besides the server files and one template link
Batches 5, 7 and 6 of docs/10-URL-2026.md (roadmap steps 72-74): the builder writes the stored path of an entity, every link, endpoint, form and picture the site prints starts at the root of the site, and a page asked by any other address - a query, a former path, case, a slash, num, the wrong host - lands on its address in a single hop.
Core changes:
- Building (core/system.php, core/classes/node/*, modules, blocks):
getSeoUrl() takes a passed route path, puts operation and id into the path and sorts the query
- the category map answers the path of a category; a missing route of an entity link is logged with its caller
- page replaces num on the public lists, the canonical keys and the title; the panel keeps num
The readers join the registry: Node lists and views, targets, sitemap, legacy map, forum, users, polls, comments
- the writer answers the path a publication gives, so the redirect after a submission and the approval mail link it
- Addresses from the root (templates, core, modules, blocks, lang/, modules/account/lang/):
- Templates take {{ root }} from Template::getView(); theme pictures getThemeImageUrl()
- Page links go through getSeoUrl(), mails through getPublicUrl(), endpoints, forms and htmx from HOME_PATH
user_info() takes the path of the user; the language constants of the account link carry href="%s"
- the broken Polish links and the links to op=reg, op=passfor and op=profil are gone
- One redirect (core/system.php, core/security.php, public/index.php):
setSeoMove() answers one absolute 301 for every reason at once, on the forced scheme and host
- GET and HEAD only; never POST, htmx, an endpoint, a module file, a refusal or another script
- the material, the topic, the profile and the poll call it right after their read and access check
- the three callers of getNodeLegacyUrl() answer through it, so a 6.2 address is one hop
- index.php keeps no 301 of its own: a path that names only the script is the start page
- The early forcessl/forcehost block keeps the endpoints, the panel and the scripts; the host keeps its port
- The forum counts a view after the access check; a missing profile answers 404, a members-only one 403
- robots.txt (public/robots.txt, core/system.php):
- The Disallow lines of folders outside public/ and the relative Sitemap line are gone
- addSitemapTask() writes Sitemap: <homeurl>/sitemap.xml after every map, other lines untouched
- Tests (tests/Unit, tests/Support):
- RouteBuildTest: the address table built from its entities, list query counts, no relative address at a path
- RouteMoveTest: each reason alone and mixed in one hop, the kept query, refusals, methods, no view counted
- The probes follow one 301 on their site; the install router sends every path to index.php
Benefits:
- A search engine moves once, from the query addresses of 6.2 straight to the final paths
- One place decides the address of a page, the redirect and the canonical
Technical notes:
- No schema change; the registry _routes of batch 1 is read only
- Breaking: every GET of a page at index.php?name=... now answers 301 to its path
- RouteRegistryTest::twoWritersRaceForOnePath fails on the MariaDB 11.7 of the work machine, before and after
Batches 3 and 4 of the plan of readable addresses before 8.0 (steps 70 and 71 of docs/ROADMAP-2026.md). The migration and the installer give every entity due an address its first path, a request at a path is cut before the database and resolved after it, and the builders of the core print addresses from the root of the site, so a page opened two segments deep finds its styles, scripts and links. The links still read index.php?...; building, the literals and the redirect follow.
Core changes:
- Migration (core/system.php, public/update.php, public/setup.php):
addRouteBacklog() gives types, categories before materials per type, forum sections, topics, users and polls without a module their first path, oldest id first, 500 per transaction; a second call adds nothing
- A material is due when Published, Disabled or Deleted with published <= NOW() in a type whose ext is not support
- The second stage of update.php runs it as setMigrateRoutes() on every run and keeps the counts in the manifest
- setup.php runs it after the Node profiles, so the seed of insert.sql gets its paths
- Parsing (core/security.php, public/index.php, core/classes/route.php):
The block of retired addresses keeps up to five segments of [A-Za-z0-9_-] of a path of index.php in $urlroute, with the flag bent for upper case, a trailing slash or /index.php/ in front; anything else answers 404 before the database, and a path after the script naming only the script keeps its 301
public/index.php resolves the segments by one grammar: a type or module is its home, a reserved operation its op, digits after it the id, any other path the registry; a former path opens its entity marked to move, a gone path answers 410, an unknown one 404; the keys replace the same keys of the query in $_GET
- Route::isRouteReserved() gives a module without a switch no operation instead of those of a Node type
- getVar() reads a scalar from $_POST and $_GET, so the keys of a path reach every reader
Addresses from the root (core/system.php, core/stream.php, core/classes/template.php, core/classes/access.php, core/classes/parser.php, core/classes/editor.php, core/user.php, modules/account/index.php, modules/forum/index.php, modules/node/index.php, public/plugins/editors/toastui/driver.php):
HOME_PATH is the path of homeurl with both slashes; the installer and the first stage of update.php take the folder of their script
getSeoUrl(), Template::getAssetUrl(), getUploadUrl(), FileAccess::getFileUrl() and the image check of Parser answer from the root; setRedirect() puts the root before a relative target
getPublicUrl() also takes an address of the site as a string and answers an absolute one as it is; the canonical address, the feed item and the images of a profile, a topic and a material go through it
- getImgText(), the image check of Parser and the feed take the root off an address before they use it
- Three checks of an existing asset ask for ?v= instead of comparing the address with its file
Tests (tests/Unit/RouteParseTest.php, tests/Support/route_probe.php, tests/Support/install_probe.php, tests/Unit/UpdateSiteTest.php, tests/Unit/NodeProfileTest.php, tests/Unit/InputVarContractTest.php and others):
route_probe.php parse: every row of the decided address table as a path, the grammar, the cut, the keys of a path over those of the query, and a page two segments deep linking its assets from the root
The rehearsal of update.php on the clean 6.2 holds the whole registry of the fixture; a clean installation holds the paths of its seed; getVar() scalars are driven from CLI
The expectations of assets, uploads, the file route and Node links read from the root; the bootstrap defines HOME_PATH
- Plan (docs/10-URL-2026.md, docs/ROADMAP-2026.md, docs/PERFORMANCE.md):
Batches 3 and 4 recorded; addresses from the root decided by the owner; the batch of the literals runs before the redirect
Benefits:
- A path such as /news/internet opens its page with styles and scripts while every 6.2 address keeps working
- The migrated production base and a fresh installation start with one current path per entity
Technical notes:
- No schema change; the stand's registry was filled once by a direct call of addRouteBacklog()
Breaking: getSeoUrl(), Template::getAssetUrl(), getUploadUrl() and FileAccess::getFileUrl() answer addresses from the root of the site (/index.php?..., /templates/..., /uploads/...) instead of relative ones
- Breaking: getPublicUrl() accepts array|string; setRedirect() roots a relative target
Breaking: getVar() no longer reads scalars through filter_input(), so a value the core writes into $_GET or $_POST is read
Literals of templates, scripts and PHP stay relative until the batch of the links; a page at a path still misses the theme images and the pseudo-cron below the path
A review of three 2027 plans brings them in line with what the tree now is: the schema conventions, the reserved names of the readable addresses, the scheduler as it registers jobs today and the settings that are already taken. Nothing is implemented; the plans only stop promising what the code would contradict.
Core changes:
- Admin UX (docs/2027-ADMIN-UX.md):
The status says that the decisions are taken and nothing is built; the dated remarks and the finished fix of the configuration form leave the plan
Eight ideas instead of nine, the dark theme of the panel no longer an open check, the dial of the list options left to its own roadmap step
- The keyboard state of table sorting and the action journal of an undo are described as they are
- Connector (docs/2027-CONNECTOR.md):
The rewrite rule of /api/v1/ stands before the fallback to index.php, and the name api is reserved in docs/10-URL-2026.md
Own-site calls of the scheduler keep working over http and loopback; jobs register in the three places the core uses today, or in the module list once docs/2027-CORE.md delivers it
- Every column takes its domain from the schema conventions of docs/NODE.md
The update check keeps upcheck and channel in config/api.php, because config/update.php holds the marks of the 6.3 data updates; the compared version is the leading MAJOR.MINOR.PATCH of $conf['version']
- The update package accepts only the tracked top-level entries of the release tag
- Vine monitor (docs/2027-VINE-MONITOR.md):
- The module stands on the floor of the release it ships with: PHP 8.4, MariaDB 10.5.2+ or MySQL 8.0.16+
Every column takes its domain from the schema conventions; moments are DATETIME in the site zone the connection runs on, timers unix seconds
An admin action is recorded by the connector audit and by the journal of checkAdminPost(); the public page is /vine; an installed site gets the tables from the step of update.php of its version
Benefits:
- The 2027 plans start from the current tree instead of an older one
Technical notes:
- Documentation only; no code, schema or configuration changes