Changelog
What changed in elemctl from release to release, grouped by day.
Notable changes to elemctl, newest first. Entries are grouped by day; the versions released that day are named in the heading. The format follows Keep a Changelog and Semantic Versioning.
Every entry ends with a link to the pull request it came from –
([#12](https://github.com/keyfire/elemctl/pull/12)). That is why changes come in through pull
requests: an entry without such a link is unfinished, because the reader has no way from the line
to the code and the reasoning behind it. Internal identifiers of the platform have no place in an
entry either – say what the behaviour was, not which id or response field was compared with what.
The link is written by python scripts/changelog-link.py <number>, which rebuilds the generated
pages of the site in the same run – writing it by hand is how the mirrors get left behind.
2026-10-08 – 0.52.0
Added
- Deploy can verify the intended application before building. The optional
--expected-app-nameand--expected-app-uricompare the live target exactly and refuse a mismatch before building or uploading. The library and MCP expose the same check. (#58)
2026-09-30 – 0.50.0, 0.51.0
Added
- A plugin option can be repeated:
Argument(..., multiple=True). The command gets every value as a list, in the order of the command line, and its MCP tool takes an array. An option without the field keeps only the last value, which is how a command given seven files uploaded one. (#54) - A positional plugin argument can take several values too:
multiple=True. They follow one another or come through itscli_aliaskey given several times, which kept only the last one, and the MCP tool takes an array. Older cores refuse it: checkplugins.POSITIONAL_MULTIPLE. (#56)
Changed
- A key repeated with a different value is refused.
--output a --output bused to write toband dropawithout a word; now the call stops with exit code 2 and names the key and both values, in plugin commands too. The same value twice passes, as do repeatable keys and flags. (#57)
Fixed
apps list --status running --status stoppedshows both statuses. The help promised several statuses, but a second key replaced the first without a word. Commas work as before. (#54)apps token-access --userandprobe --cleanuptake the key several times. A second key replaced the first without a word. Now every user and every probe gets an entry of its own, a failure of one does not stop the rest, and the MCP tools take a list as well. (#55)
2026-09-29 – 0.47.0, 0.48.0, 0.49.0
Added
builds uploadwithout a project id names the project the build went into. The answer printedproject-id: nulleven though the server had named the project. Nowproject-idand theprojectfield carry it, and a line on stderr says whether the upload created the project or found it by theИдofПроект.yaml. (#52)CommandContext.surfaceshows where a plugin command was called from. It isSURFACE_CLIin a subcommand,SURFACE_MCPin an MCP tool andNonein a context built by hand. Plugins used to guess fromcontext.log. (#47)--quietworks anywhere in a command. After a plugin command it was refused as an unrecognized argument. It silences progress and warnings on stderr, a plugin’scontext.logtoo, but not the answer, errors or exit code. (#47)apps export-extensionsaves the build of an extension applied to an application. The MCP toolexport_extensiondoes the same. If the application has no such extension, the error lists the ones it has. It needs Console API 2.1. (#48)apps exportsaves the build an application runs. The MCP toolexport_appdoes the same. A build uploaded to a project comes back with the server’s number.apps export-extensionsaves applied extensions. (#49)
Changed
apps create --version-idfinds the project of the build itself. When the project fromELEMENT_PROJECT_IDdoes not list the build, the command looks it up in the other projects and takes that one. With an explicit--project-idit refuses and names the right project. The MCP toolscreate_appandensure_appdo the same. (#52)builds listof an extension project says that nothing cleans it up. The builds of such a project stay until someone deletes them, so a gap in its numbering is a deletion by hand. (#52)- A wait stops after a minute of the
UNKNOWNstatus. It used to run out the whole timeout, five minutes fordeployand ten for a new application. The console also reportsUNKNOWNfor passing states, so the wait gives it a minute and then stops with an error that explains it. (#47, #49)
Fixed
self-update --stop-holdersno longer ends the commands of other sessions. A command waiting for a pipeline got cut off with exit code 1 and no verdict. Now the flag stops the MCP servers and names each running command by pid and command line. If one of them holds the files, the update refuses and asks you to wait;--stop-holders=allends the commands as before. (#53)- An upload without a project id no longer renames a project silently. When the server puts
the build into the project with the same
Идand another name,builds uploadandprobewarn about it and give the former name in the answer. The name comes from the presentation inПроект.yaml, not from the manifestName, as the documentation said. (#52) ROUTE_NAMEis back inelemctl.registry. The previous release renamed it toROUTE_NO_PROJECT_ID, and code importing the old name broke. The old name is an alias now. (#52)- A plugin can no longer take a name the core uses. A plugin’s own
--timeoutnever got its value, its--env-filekept the MCP server from starting, and an argument namedhandler,--helpor-hbroke the call or the whole CLI. Such a plugin is now left out and named. (#48, #49) deploykeeps up with the server’s numbering after the newest build is deleted. The server counts deleted builds, which the build list does not show, sodeploynow also reads the local registry of uploads. A deleted upload from another machine stays unseen, and thendeployjust reports the server’s number. (#48)buildpacks an extension project as an extension. Its manifest now hasProjectKind: ExtensionandManifestVersion: 1.1, as the console writes it, andbuild --kindtakesextension. Before, the server refused even a hand-corrected archive with “Unknown project kind”. (#49)- A path an old server does not know no longer causes a second sign-in. The client took the 401 “Handler of HTTP request … not found” for a rejected token and signed in again. Now it reads the text first and reports the method as unknown. (#49)
builds listdescribes numbering more accurately. For an empty list it says the project has no builds, and for builds without numbers that there is nothing to judge by. A gap that is both a jump and a deletion is named as both. (#49)- The warning about a disabled certificate check no longer names a setting nobody set. It named
ELEMENT_TLS_VERIFY=falseeven when the check was turned off in code.UrllibTransportnow takes the reason intls_off_reason. (#49) - An applied extension no longer looks like a rollback. The card of the application keeps
naming the application’s build, so
apps apply,deployandverify-deployreported every extension apply as rolled back. Now an extension build is checked against the extensions of the application, and a server without Console API 2.1 gets “cannot verify” instead. (#50) apps ensurerecognizes an extension that already runs the build. It compared the build with the card of the application and answeredapplied: false. Now it checks the extensions of the application, likeapps applyandverify-deploy, and so doesensure_app. (#51)
Documentation
- The specification lists the right flags accepted after a subcommand. It had
--version, which is not accepted there, and lacked--lang, which is. (#48) - The documentation explains when the platform deletes unused builds. It happens after a project’s application applies a build. The platform then checks up to ten builds of that base and keeps the builds in use, the highest-numbered one, and release and protected ones. Uploads by vendor and name are protected at once. (#49)
- A project is found by the
Идof its description, not by the vendor and the name. An upload with the sameИдand another name goes into the old project and renames it, and a newИдwith a pair that is already taken gets a 409. (#50) - Housekeeping never touches the builds of an extension project. It starts from the application’s build, so an extension project keeps every build until someone deletes it. (#51)
- An upload that names no project is called an upload without a project id. The server finds
the project by the
Ид, not by the vendor and the name. The registry writes the route asno-project-idand still readsvendor-namein older lines. (#51)
2026-09-27 – 0.46.0
Added
deployrefuses to drop a catalog whose description is gone. The schema guard read only the files on disk, so a catalog, a document or a register removed from the sources took its whole table away unnoticed. Now it lists the files of the applied commit and refuses such a removal like a narrowing, unless--allow-data-lossis given. (#45)- The MCP tool
deploytakesallow_data_loss. A narrowing the schema guard refused could only be let through from the CLI, and a client of the MCP server had no way to say that the loss of data is acceptable. The refusal now names the parameter as well as the flag. (#45) deploysays when the server numbers the build its own way. An upload into a project gets the next number of the project’s base from the server, whatever version the archive carries, and the report saidokwith onlyversionandapplied-versionapart. A version the server will not keep is now named before the build, a renumbered build right after the upload, and the report carriesassembly-versionandrenumbered. (#45)apps usersshows who is connected to an application. The list was seen only inside a refusal ofapps token-access. The command and the MCP toollist_app_usersprint it the way the platform gives it and count the administrators and the users with token access. (#46)deployrefuses a catalog described anew under a newИд. A description made again in its own file is a new element for the platform: checked live, the catalog came out of the apply empty, while the guard named only its fields. Now it is refused as a removal, and the refusal names the formerИдto give back. (#46)
Changed
- The schema guard of
deploytakes a missing commit from the registry of uploads. A build that created its project carries no commit on its card, so the guard stepped aside while this machine remembered the commit. Now it compares against that commit and says where it came from. (#45) - The registry of uploads keeps the newest thousand uploads. It grew by a line with every
upload for as long as the machine deployed. Once it grows past the limit by a tenth, the next
upload cuts it back;
ELEMCTL_REGISTRY_LIMITsets another number, and0keeps every one. (#45) - The specification lists the statuses
DeletingandUNKNOWN. The reference gives the status of an application as a plain string, and the list of the specification lacked two statuses met live:Deletingwhile an application is being deleted, andUNKNOWNon an application whose database was gone. A wait for a stable status does not takeUNKNOWNfor one. (#46)
Fixed
probe --cleanupremoves a probe that was given its own name and version. Neither mark of a probe was there, the cleanup refused, and the application, the builds and the project had to go by hand. The registry of uploads now remembers the application a probe creates, so the cleanup knows it and takes the builds deployed into it along. (#46)- An upload takes the build into the space it was given. The client sent the space as
SpaceId, a spelling the server does not read, so the space of a new project never reached it and a wrong one went unnoticed. An upload into a project now sendsspace-id, and a new project goes into its space throughPOST /spaces/{space-id}/projects. (#46) builds listtells a jump in the numbering from the housekeeping. A build uploaded by the vendor and the name keeps the number of its archive, and the count of the project goes on from it, while the count line put the hole below it down to deleted builds. The registry of uploads now remembers which way each build went, and such a hole is named as a jump. (#46)
2026-09-25 – 0.45.0
Added
- A build can be traced to the code it came from. An upload into a project now sends its
commit the way the reference spells the parameter, and the server keeps it on the build card.
The branch, the state of the tree and the directory the platform does not keep, so a local
registry of uploads remembers them;
builds list --briefandapps getname the source of each. (#42) apps token-accessopens the HTTP services of an application to a token. A call of a service with a user’s token was refused with a 500 “Token access is denied” until the flag was switched in the control panel. The command and the MCP tooltoken_accessshow the flag, switch it and read it back, and name a user who is not connected to the application. (#43)probe --cleanupremoves a kept probe in one command. Taking a probe away by hand went wrong: a barebuilds deletelooked in the project of the environment, not in the probe’s. The new command starts from the application, deletes it, its build and an empty project in the platform’s order and refuses an application no probe left; the report ofprobe --keepnames that command and the steps by hand. (#43)
Fixed
- A server that is still starting is called what it is. While its console was coming up,
every command answered with a bare 404, and
deploydied on the list of builds. That answer now says the server is starting and to repeat once/consoleanswers 302, anddeploywaits for the console by itself, for up to--server-start-timeoutseconds. (#42) - The schema guard of
deploysees tabular parts. A build without a tabular part took its rows along while the guard said nothing. A removed tabular part, attribute or resource is now named before the build without stopping it, a narrowing inside a tabular part is refused like any other, and a skipped check gives its real reason instead of always blaming a missing commit. (#42) self-updatesees a release published minutes ago. Both PyPI listings may name the previous version for a while after a release, and the command answered “already current”. It now reads both listings and the pages of the next versions, says when the sources disagree, and never replaces an installation with an older release. (#43)
2026-09-24 – 0.44.0
Added
probechecks the manifest before it builds anything. A probe project withoutПредставлениеwas refused by the console with a bare 500, one withoutЯзыкРазработкиnever got an application, and aЯзыкПоУмолчаниюwithoutЯзыкиЛокализацииwas refused with no cause named. Now the probe stops before the build and names each missing key with the line to add. (#41)
2026-09-23 – 0.43.2
Changed
- Python comments and docstrings consistently use hyphens. Ordinary string literals remain unchanged. (#40)
2026-09-22 – 0.43.1
Fixed
self-updatereports an incomplete PyPI response without touching the installed package. The simple index, metadata and wheel downloads missed HTTP parser failures, so an interrupted response could end in a traceback. (#38)- Connection setup rejects a base URL without an HTTP scheme. An address without
http://orhttps://used to reachurlliband fail with a rawValueError. (#38)
2026-09-19 – 0.43.0
Added
probeanddeployname the server log when the server refuses without a compilation error. The platform may answer a failed create or apply with “Contact administrator for details” alone, and the report then had nothing to act on. The newhintfield says to look for the lastCaused byline in the server’sserver.log, whereSrcPath:beside it names the file. (#36)
Fixed
deploy,apps applyand the cleanup inprobeno longer fail on the first dropped read of the application card. Only the wait for a new application took such a read for a missed poll, and one dropped connection failed a whole deploy or left the build ofprobebehind. All three waits now poll the card the same way and give up only when their time is up. (#37)- An answer cut short halfway no longer ends the command with a traceback. The transport
caught only
OSError, whilehttp.clientreports such a break and a garbled status line with exceptions of its own. Both now count as network errors: the command prints the usual error JSON, and a read that is repeated after a dropped connection is repeated after them too. (#37) apps createandapps ensurerefuse a--version-idthat is missing from the project’s build list, before creating anything. The platform deletes the builds nobody uses and answered a create from such a build with a bare 400 “Can’t create application”, which looked like a limit on the number of applications. The refusal now names the cause and the build a running application of the project runs, and the MCP toolscreate_appandensure_apprefuse the same way. (#36)- A wait in
apps create --waitandapps ensure --waitthat breaks off no longer loses the created application. A dropped connection while the task list was read ended the command with a bare network error, and the id of an application that came up minutes later had to be looked up by name. The answer now keeps the id beside await-errorfield and exit code 1, and a broken read of the task list is made again. (#36) - A plugin that fails to load no longer takes the CLI down. A plugin written for a newer
core failed in the call of its factory, which nothing guarded, and every command, the core
ones included, ended in a Python traceback. The plugin is now left out and named on stderr
and in
elemctl plugins, a call to its missing command gets the usual JSON refusal, and the MCP server starts with the rest. (#36)
2026-09-18 – 0.42.0
Added
- A positional argument of a plugin command accepts a key synonym,
cli_alias. A plugin command declares such an argument positional only, sowiki-get --page 123used to fail with “unrecognized arguments” even though the same key works onwiki-publish. A secondArgument("--page")was not a fix either: the MCP tool would then carry twopageparameters. The synonym is CLI-only – the MCP tool schema still has the one parameter it always had – the positional form keeps working, and giving both at once, or neither of a required argument, is refused by the parser. A plugin that declares nocli_aliasbehaves exactly as before. (#34)
Fixed
ELEMCTL_NO_PROXYis now also read from the stand’s own.envfile. An MCP tool call only carriesenv_file, and there was no way to set a process variable for one stand among several the same server process serves – so a call to a local stand kept failing behind a proxy that cannot reach it, with the CLI’s ownELEMCTL_NO_PROXY=1workaround out of reach. The environment variable still wins when one is set, and a value read from one stand’s file never reaches a call to another stand served by the same process. (#32)builds listnow prints its count and truncation notes after the answer, not before it – the orderapps listalready used. A caller capturing both streams as one (2>&1, orstderr=STDOUT, a plain way to catch everything a command printed) used to see those notes ahead of the JSON answer forbuilds list. The two commands agree on the order now, though reading a merged capture whole is still not guaranteed – only stdout read on its own is safe to parse as a document. (#32)- The MCP server’s client cache now notices an edited
.envfile. The cache used to be keyed byenv_filealone and held its client for the life of the process, so a stand’s file edited after the server started – addingELEMCTL_NO_PROXY=1, say – stayed invisible until a restart; the default file (noenv_fileat all) had the same limit. The cache now also tracks the file’s modification time and size, so an edit reaches the very next call for that stand, and anenv_file(or a startup--env-file) that goes missing after being cached surfaces the same clear error it always gave, rather than the cache quietly going on with the client that last worked. An explicit--env-fileis still checked as soon aselemctl mcpstarts, exit 1 before the server ever runs; every other default-stand configuration problem surfaces on the call that hits it instead. (#33) Config.from_env(no_proxy=...)no longer raisesTypeError. Every other field takes an explicit argument ahead of the environment and the file, exactly as the docstring promises;no_proxywas the one exception, because its override was never taken out of**overridesand fell through to the “unknown configuration parameters” check at the end. The explicit argument now wins overELEMCTL_NO_PROXYand the file’s own copy of it, the same as every other field – there were no callers passing it yet, which is how the gap went unnoticed. (#35)- Four small things around the MCP server’s client cache.
create_appandensure_appused to resolveclient(env_file)several times per call – up to four inside the shared_create_app, plus the tool’s own lookup – so a single call could in principle run its steps against two different clients if the stand’s file changed mid-call; each tool now resolves the client once and passes it through. The client a call is replacing used to be released BEFORE the new one was stored, so an exception from itsclose()left the cache pointing at the one that had just failed to close instead of the new, working client; storing now comes first. A lock now guards the cache dictionary itself – every call still runs to completion before the next one starts, so nothing exercises the race today, but a future threaded or async dispatcher would make it real. The fingerprint (modification time and size) still cannot tell a file that turned unreadable without being edited from one that has not changed at all; closing that gap would mean reading the file on every lookup instead of only on a change, and even a lighter, POSIX-only version would still miss it on Windows – the trade-off is now spelled out where the fingerprint itself is built. (#35)
2026-09-13 – 0.41.0
Added
- A plugin command sets its own exit code. The CLI exits with the integer from 0 to 255 that
the result puts in its
exit-codefield, so a pipeline can tell “differences found” from “a step failed” by the code alone. Without the field,"ok": falsestill means 1. (#31) - The documentation guard catches a sentence that explains a change by naming who asked for
it. The repository has one author, so such a sentence tells the reader nothing about the
change. The guard reads both editions of the documents and the comments and docstrings of
src,scripts,testsandtools. (#30) - The conventions guard requires a process started from
srcto name its stdin. The check reads the shipped package alone. A script, a tool and a test run from a console, and a console stdin is safe to hand on. (#29) tests/test_conventions.pycatches a test shadowed by a namesake. Python keeps the last of two definitions with one name, so the older test stops running while the count still grows. The check names the line of the newcomer to rename. (#28)
Fixed
- A child process no longer inherits the stdin of the MCP server. On Windows a child holding
the client’s pipe never exits:
git status --porcelainsat out its whole fifteen-second timeout, and the build called git unavailable. Process starts insrcnow passstdin=subprocess.DEVNULL, and the same tool call went from 47 seconds to 0.8. (#29) .gitattributesholds the line ending for the whole repository. The line* text=auto eol=lfkeeps text files in line feeds on any machine. The READMEs thatscripts/sync-docs.mjsassembles used to come out with mixed line endings. (#28)
Documentation
- The shared guard is pinned to
docsguard@v0.7.1. That release adds a dictionary of the borrowed words the Russian edition keeps drifting into:пин,прогон,билд,дефолтand eleven more. The pin went up on its own, ahead of anything that uses it. (#26) - The documentation guard reads the Russian texts against that dictionary. It finds
docs/*.ru.mdby itself, and the root documents are named inscripts/check_docs.py:README.ru.md,CHANGELOG.ru.mdandCLAUDE.md. A word in backticks or inside a fenced block is not a finding. (#26) - The words the changelog names are in backticks now. The entry of 12 September lists the words that left the text, and the guard read them as jargon. The Russian page writes the XBSL button caption the same way, as the name of a command. (#26)
- The shared guard is pinned to
docsguard@v0.8.0. The dictionary of borrowed words grew by six:дашборд,бэкенд,лаунчер,мейнтейнер,топ-объект,легаси. (#27) - The guard reads the Russian strings of the sources against that dictionary too. The help
of a command and the text of an error reach a reader the way a page reaches the site: they
land in a terminal the moment somebody runs the tool. Two files are named, the message catalog
src/elemctl/i18n.pyandsrc/elemctl/mcp_server.py, whose tool docstrings an MCP client shows as the description of each tool. (#27) - The word
лаунчерleft the entry aboutself-update --stop-holders. It is about the program that starts the command, and the command runs as its child. The same word is corrected intests/test_selfupdate.py. (#27) - The shared guard is pinned to
docsguard@v0.9.0. In that release the source checks stopped falling over on a file that begins with a byte-order mark. Editors on Windows write the mark without being asked,astanswered it with aSyntaxError, and the findings from every other file went down with it. The shadowed-test guard comes from the same release. (#28)
2026-09-12 – 0.40.1
Added
tests/test_conventions.pycatches a text file written without an explicitnewline. It scanssrc/,scripts/andtools/, parses the sources withast, and countswrite_textor anopenin text write mode as a write. The reading comes from the shareddocsguardpackage. (#22)
Changed
- The
--helpstrings no longer shout in capitals.verify-deploy,--project-id,--force-renameand thetasksepilogue used capitals for emphasis, which reads as shouting on a page. The Russian strings dropped two borrowed words along the way, “таймаут” and “деплой”. The command reference is rebuilt from the same strings. (#25) - The three coverage checks of the documentation guard share one set difference. Tools,
environment variables and archive extensions were each checked against the docs by a copy of the
same code, and a copy that had gone stale passed in silence. All three now judge through
coverage_problemsfromdocsguard. (#20)
Fixed
scripts/changelog-link.pyno longer rewrites the changelog with the platform’s line ending. One appended link handed back four files with every line changed. The same omission is fixed inrender-diagrams.py, the token cache, the pipx metadata and the adapter index. (#22)
Documentation
- The documentation is rewritten in plain language. Long sentences were cut, and the stock turns of phrase, the parenthetical asides and the words in capitals are gone. The English edition now reads as English rather than a word-for-word translation of the Russian one, and the Russian changelog lost the borrowed words the dictionary lists. (#24)
- The shared guard is pinned to
docsguard@v0.5.0. That release adds a second convention for the sources: a text file written without an explicitnewlinetakes the platform’s line ending. The pin goes up on its own, ahead of anything that uses it. (#21)
2026-09-11 – 0.39.0, 0.40.0
Added
get_buildreturns the card of a single build over MCP. The listing was there, the card was not, so an agent after a manifest name, a vendor or a build comment had to drop to the CLI. The tool is addressed by build version, and an id works too. (#14)apps createandapps ensureverify the apply themselves, through--verifyand theverifyparameter over MCP. The check used to live only indeploy,apps applyandverify-deploy, so a freshly raised stand needed a second call. The report arrives in theverifyfield. (#5)
Changed
- The summary under a build listing judges by gaps in the numbering, not by a threshold of
thirty. The platform hands out the numbers of one base version in sequence, so a missing
number is a build it has already removed.
ASSEMBLY_STORE_LIMITwent with the threshold. (#13) buildandinspectrefuse the connection options.--env-file,--base-urland the credentials used to be dropped without a word, which made the call look like a build tied to a stand. A behaviour change: it is a refusal with exit code 1 now. (#7)- The build listing is described the way the platform really works. We thought it capped builds per project and pushed the old ones out; in fact it deletes the builds nobody uses. The listing summary and the Console API pages were rewritten. (#6)
--waitends with a check instead of taking the card on trust. A failed apply rolls back to the previous build and the application comes up running all the same, which the card does not show. A behaviour change: a failed check exits 1, and--no-verifybrings the plain wait back. (#5)
Fixed
- The correction about automatic build deletion reached the remaining pages and hints. The CLI
requirements, the MCP server pages, the
list_buildshint and the code comments still told of a store limit. There is one model now. (#9) builds getworks at last: a build card is addressed by its version. Our pages claimed the method takes a UUID only, so the command turned the version into an id and got a 404 back. Live calls on two installations proved otherwise, and both forms are accepted now. (#8)
Documentation
- The shared
docsguardpackage took over the process-encoding check. It was written here, though the engine and the bridge start processes the same way and face the same silent failure. What stays here is the list of folders; the pin goes up tov0.4.0. (#19) - The shared guard is installed by tag, and raising the pin is a change of its own. On
@mainadocsguardcommit landed in a run here in the middle of unrelated work, and a red run caused by no commit of ours is one nobody reads.tests/test_workflows.pyfails when the workflows name different tags. (#18) - A process read as text has to name its encoding, and a test now says so. A new script did
not, and the failure was silent: the Russian page names came back as replacement characters
while the exit code kept reporting success. The convention is written down in
CLAUDE.md. (#17) - The claim mechanics moved into the shared guard. One fact told in several documents at once
is not an elemctl problem: the engine and the bridge keep their documentation the same way.
Claim,claim_textsandclaim_problemscome fromdocsguard. (#16) python scripts/rebuild-docs.pyrebuilds every generated page in one run. Two generators write four pages, and each was a thing to remember on its own; the mirrors went first. The command answers with an exit code, so a failing generator no longer hides the other. (#15)- The guard catches one statement told in two different ways. A fact about the platform lives
in the specification, on the Console API and MCP pages, in the README and in the code comments
at once, but it gets corrected in one of them. Such facts are listed as
CLAIMSinscripts/check_docs.py. (#12) - The pull request link and the mirror rebuild are one step. A link can only be written once
the pull request exists, so it arrives in a commit of its own, and the mirrors kept being
forgotten.
python scripts/changelog-link.py <number>does both halves. (#11)
2026-09-10 – 0.37.0, 0.38.0
Added
- The
verify-deploycommand – the verification apart from the deploy. The library and the MCP tool had the mechanism; the CLI did not, so CI scripts rebuilt the same check by hand out ofapps getandtasks list. The command now answers with the reportdeploygives and exits 1 on a failure; it deploys nothing itself. - The
--jsonflag: the answer alone reaches stdout. Scripts parsed the output by hunting for the first brace, and one stray line ahead of the JSON broke the parse. While a command runs, stdout is now swapped for stderr – no line of a plugin, no warning of a library slips into the stream. The flag is global and accepted in any position.
Changed
builds listsays what kind of listing the reader is looking at. “30 of 30” read as the project’s whole history, while the platform keeps only so many builds per project. The count line now says whether the listing is complete or has hit the store limit. A behaviour change: the MCP toollist_buildsanswers with{total, shown, summary, builds}, not a bare array.- A group called straight with a flag gets a hint about the action. argparse answered
elemctl tasks --app-id ...with “invalid choice”, and the reader went looking for the mistake in the value while the missing word waslist. The refusal now prints the form of the call and the actions of the group, andtasks --helpspells both forms out. --latest-buildpicks the newest build by its created stamp, not by the highest counter: after a base version bump the old base keeps the higher numbers, and an application created “from the latest build” would have got an old one.
Fixed
- The command reference no longer breaks a word wrapped on its hyphen. argparse wraps a long
word on the hyphen, and joining the lines with a space broke the very name under discussion:
the reference read
elemctl tasks get- group TASK_ID. The joining rule is now shared – it covers the parser’s description and its epilogue too. - The build counter restarts when the project’s base version changes. The auto-increment
took the highest counter of all the project’s builds: one stray
1.0.1-19001pushed every later build to 19xxx, and bumping the project to1.0.2would have continued at 19024. The counter is now taken among the builds of the project’s own base version, so a bumped project starts from1.0.2-1.
2026-09-09 – 0.36.0
Changed
apps listhides the deleted applications and says how many it hid. The platform keeps them in the list under theDeletedstatus with their former id: a stand a few months old answered with three hundred cards of which a handful were alive, and--briefdid not drop them – the answer had to be filtered by the caller’s own code. The deleted ones are now hidden,--include-deletedbrings them back, and--status deletedlifts the hiding on its own: a filter that would answer with nothing is worse than no filter. A cut nobody is told about is a trap, so the command ends with a count line on stderr (“7 live of 324”, plus the number shown when a filter narrowed the answer further); stdout stays the same JSON array scripts parse.- The
list_appsMCP tool takesinclude_deletedand answers with an object. The cards (applications) come with the counterstotal,live,shownand a readysummaryline: an agent sees only the JSON, and the cards that were cut have to be named, otherwise the listing reads as “the stand holds seven applications”. The former answer – a bare array – is gone;find_appand the name-to-id resolution still see the deleted applications and skip them themselves.
2026-09-06 – 0.35.0
Added
projects list --nameand--include-deleted; thelist_projectsMCP tool takes the same filters. The platform keeps deleted projects in the list under a flag, so a stand a few months old answered a check for a project name with over a hundred cards, most of them deleted. The deleted ones are now hidden unless asked for, andnamenarrows the list by a case-insensitive substring on the client – the wayapps listalready does it.
2026-09-05 – 0.34.0
Fixed
- The application is addressed both positionally and with
--app-id. Theapps get,apply,delete,start,stopanddebugcommands took the positional form only, whiledeployandapps ensuretook the option only, so a call written the other way answered with usage instead of doing the work. Two DIFFERENT references in one call are refused: silently preferring one would send the command to the wrong application.
2026-08-31 – 0.33.0
Added
- The schema guard covers the dimensions of a register. The records are keyed by them, so changing a dimension’s type – or removing it – makes the platform convert the records, collapse the values and fail the apply on their uniqueness, with a silent rollback behind it. A probe cannot foresee that: a throwaway application has no records to convert.
- A SOAP service client’s description goes into the build.
<Client>.Wsdl.<n>and<Client>.Xsdlie next to the project element rather than inРесурсы, and their extensions are outside the allowlist – the archive used to lose them silently. A client whose description is missing is named before the upload. apps list --status running(several statuses separated by commas). A stand a few months old holds hundreds of applications of which a handful are alive, and asking what runs here should not cost the full listing.- The deploy report carries the refusal as lines.
problems-linesholds the platform’s text broken into plain lines: JSON escapes a multi-line refusal intoandexactly where it has to be read – which object, which keys stopped being unique.
Fixed
- An apply waits out a busy application instead of giving up on it. A deploy right after another one used to fail with “the application is busy” AFTER the build had been uploaded; the wait is short and only fires on the busy wording, so a missing application still fails at once.
- A probe stops at a refused compatibility mode. A stand older than the project refuses the whole project and then complains about types and properties of that mode in files the change never touched – hundreds of lines that read like a verdict on the code. The refusal is now named for what it is, and what followed from it is counted rather than parsed.
2026-08-30 – 0.32.3
Changed
- The package description names the probe. The PyPI summary and the site descriptions listed applications, builds and deploys and said nothing about the compilation check the server runs without risking the working application – a reason of its own to pick elemctl.
2026-08-27 – 0.32.2
Added
- A release leaves a GitHub release card. After a successful PyPI publication the workflow creates a GitHub release whose body is this changelog’s section for the released versions – repository subscribers now see the actual “what’s new” in their feeds instead of a bare tag.
2026-08-26 – 0.32.1
Changed
- The local-cloud example in the documentation points at a placeholder domain in the
reserved
.examplezone: a configuration example must not name any real server. - The changelog is trimmed to two-four lines per entry: what changed and why it matters; the history of a finding stays in the commits and the documentation.
2026-08-24 – 0.32.0
Added
apps apply [APP_ID] VERSION_ID– apply an uploaded assembly and verify the result. Applying used to be reachable through the MCP tool alone, while long operations are the ones that want a CLI run in the background. On a failure the platform silently rolls the application back to the previous build and starts it, so the command checks what actually landed and exits with 1 when the assembly did not.apps ensureanswers about the assembly too. The creation flags act on creation alone, so--version-idwas not applied to an existing application - silently. The answer now carriesappliedandapplied-version-id, stderr names the command that brings the application to the requested assembly, and--applydoes it in the same run; the MCP toolensure_appgrew the same field.
Fixed
builds uploadno longer refuses an assembly of its own project. Two different things were compared: the technical name of the manifest and the presentation the console shows the project under - they could only match by accident, and every correct upload looked like a rename. What is compared now is the project presentation from the descriptor inside the archive; a foreign assembly is refused as before.- The refusal explains
--new-project. The wording “starts a separate project” scared people away from the path that worked: the platform recognizes a project by the vendor and name of the manifest, so an assembly of the same project lands in the existing one.
2026-08-18 – 0.31.0, 0.31.1
Added
- A connection with verification off says so on every run.
ELEMENT_TLS_VERIFY=falseis written into.envonce, and a call that checks nothing looks exactly like a call that passed the check. The warning goes to stderr, so JSON piped out of stdout stays intact. - TLS is configurable: a private CA and the strictness of the checks. The RFC 5280 strict
profile enabled in Python 3.13 rejected the internal CA of a private cloud.
ELEMENT_CA_FILEadds a PEM file to the trust store,ELEMENT_TLS_STRICT=falsedrops the strict profile alone, andELEMENT_TLS_VERIFY=falsestays the last resort; a typo in a value is a configuration error rather than silently weakened TLS. Contributed by @dvkuchin (pull request #4).
2026-08-14 – 0.30.0
Changed
builds listanswers with ten recent assemblies. An old project accumulates thousands of them, and the question “which commit is the applied build from” was answered by truncating the full list.--limitsets the slice (0for all), stderr says how many of how many are shown, and--briefleaves the id, the versions, the date, the branch and the commit; the MCP toollist_buildstook the same parameters.
Fixed
- A probe assembly can no longer win the choice of “the latest build”. The random token of
its version comes out all digits about once in forty draws, and such a version reads as a huge
counter -
--latest-buildwould pick the probe instead of the real one. An all-digit token is now redrawn; caught live in CI.
2026-08-11 – 0.29.0
Changed
- A missing
.envfile is named by its absolute path and the current directory. A relative--env-fileresolves against the current directory rather than--project-dir, and in a background run the refusal read as an unreachable stand. probestates what it needs of the project layout. The directory has to sit as{repository}/{Vendor}/{Name}/Проект.yaml- which used to be learned from the error afterwards.
2026-08-10 – 0.28.0
Documentation
- The list of MCP tools is complete. Seven of the twenty-four were named nowhere but in the sources: the page made do with “and others”.
- The environment variables are described in full - together with
ELEMCTL_NO_PLUGINS, the CI variables a build takes its run number from, and the systemLC_ALL/LANG. - Stale claims corrected: the common flags are accepted in any position,
.htmis one of the archive extensions, and inside aРесурсыdirectory there is no extension filter at all. - One source per text. The README sections that repeated the site pages had drifted both
ways; the page is now the source, and
scripts/sync-docs.mjsinserts its section into the README between markers. - An architecture diagram (
docs/architecture.svg) - the surfaces, the engine, the deploy cycle with its silent rollback, and the probe; the palette follows the reader’s theme. - The guard
scripts/docsguard.pyand thedocsjob in CI. It judges what is mechanically checkable: a tool with no row in the table, an environment variable the code reads and nobody described, a stale changelog mirror. Every class of finding is provoked by a test.
Added
deploytakes an application name as well as an id - the wayapps get,apps start,apps stopandapps debughave long done.
Changed
- The VS Code extension left this repository. Debugging 1C:Element applications is part of
the XBSL extension as of its 0.57.0, together with the
deploy button: while they were apart, the path to elemctl and the application id had to be set
twice. The elemctl side stays here -
apps debug,debug-adapterand theelemctl.debug_adapterentry point.
2026-08-07 – 0.26.0, 0.27.0
Added
- Projects with English artifacts build and deploy.
build,deployandprobefind aProject.yamldescriptor as readily asПроект.yaml, the archive reader takes English keys and values, and the schema guard knows that the two spellings of a primitive type name are ONE type: translating a description does not read as a data-destroying change.
Changed
- Uploading an assembly no longer sends the commit and the branch. The documented method does not take them, and the server ignores what is sent: the commit on an assembly card comes only from the project’s link to its repository. Inside the archive both still travel - the build writes them into the manifest.
- A skipped schema check is named in the report. The
schema-checkfield answersclean,allowedorskipped:<reason>: a check that did not run must not read as a check that passed.
2026-08-03 – 0.25.0
Changed
- An assembly with a foreign name is refused rather than merely warned about. The console
shows a project under the name of the last uploaded assembly, and deleting the assembly does not
bring the former name back. The ways out are named in the message itself:
--force-renameif that was the intent,--new-projectif the assembly belongs elsewhere.
2026-07-31 – 0.23.0, 0.24.0
Fixed
self-updateright after a release no longer misses it. The file list came from the PyPI JSON metadata, which lags the publication by minutes: inside that window the command answered “already current”. The list now comes from the simple index (PEP 691), and versions are ranked numerically.
Changed
- The MCP server works with both majors of the
mcppackage, the pin is gone. Inmcp 2.0.0the server class moved without leaving an alias, and a fresh install would not start at all. The import tries both locations, two helpers hide the difference in reading the answers, and CI runs the suite against both majors. serverInfonames the version of elemctl itself, not of themcppackage.
2026-07-30 – 0.22.0
Added
- A build takes in library projects lying next to the application, transitive dependencies included; a declared library with no local project stays an external dependency of the platform. Contributed by @dvkuchin (pull request #3).
Fixed
- A proxy in the environment no longer passes a live stand off as a dead one. A proxy that
cannot reach the stand refuses exactly the way a stand that is down would. A proxy that clearly
will not help is now bypassed,
ELEMCTL_NO_PROXY=1bypasses it everywhere, and a failed call that did go through a proxy names it. self-update --stop-holdersno longer kills its own process tree. The command runs as a child of the launcher, indistinguishable from a holder by name, so it offered to kill itself and broke the update halfway.
2026-07-28 – 0.20.0, 0.21.1
Added
- Creating an application ends with the way into it.
apps createandapps ensure(and their MCP twins) gained asign-infield. A new application gets its own empty user list, so the accounts used to sign in to other applications do not work here: the way in is a control panel account. Guessing is not an option - a user has a failed-attempt counter.
Changed
self-updateno longer replaces a working installation with nothing.pip install --upgradehit a file held by a live session, removed the package, failed to unpack the new one and said nothing. The installed package is now renamed first, holders are named by name and pid, and the previous installation is restored unless the new one proves it imports.
2026-07-27 – 0.19.0
Added
user-lists calculation-rules- reapply the rules a user is assembled from. Recreating the sign-in service resets them, and reading never returns them, so the report says it plainly: what was sent, that the request was accepted, and that the value cannot be confirmed through the API - only by the console or a live sign-in.
Fixed
- A build made on a CI runner recorded the branch as
HEAD. A runner checks out a commit rather than a branch. The name now comes from the CI environment, and when nobody names the branch the field stays EMPTY: an honest blank beats a word that looks like a branch name.
2026-07-26 – 0.15.0, 0.16.0, 0.17.0, 0.18.0
Added
deploynames its target and where the target came from. The report gainedapp-name,app-id-sourceandproject-id-source, and the target is announced in the first progress line- before the build, while a deploy aimed at the wrong application can still be interrupted.
- A build names the files that did not make it in (
skipped-filesplus a warning). A file with an extension outside the allowed list enters the archive only from aРесурсыdirectory; anywhere else it is lost silently, and the platform reports it only on apply. - A guard against destructive schema changes. Narrowing an attribute’s length or changing its
type RECREATES the object’s data, so
deploycompares the sources against the commit of the applied build and refuses before the build;--allow-data-lossskips it. With nothing to compare against, the guard says so and does not stand in the way: not being able to compare is no proof of danger. user-lists- the sign-in settings of a user list, the ones usually looked up in the console:list,get,self-registrationandpassword-login. Without a flag both setting commands only read the state; on the MCP side,list_user_listsandconfigure_user_list.- The
elemctl.commandsentry point group - a plugin package brings commands of its own, and one declaration becomes both a CLI subcommand and an MCP tool. That is where a command that knows about someone else’s environment belongs, since it cannot live in a public core. elemctl probe- an isolated compilation check. Compilation on this platform is server-side and happens on apply, so the probe builds an archive, creates a throwaway application from it and cleans up after itself. Errors come back parsed: file, line, column, environment and text.
Fixed
- A common option is accepted after a subcommand too -
elemctl deploy --env-file .envno longer fails with the argparse “unrecognized arguments”. - A token the server rejected no longer lives in the cache. This server rejects a bad token
with 400 rather than 401, naming the reason in
error_description; such a refusal now drops the cache file and retries once. - The assembly is deleted after its application is gone. The platform refuses the deletion while the application created from that assembly is alive, so the probe waits for it to disappear first.
Documentation
- The Console API contract gained the user lists (section 4.7) and what the API does not carry at all: the composition of the authentication forms and the “connect users automatically” setting.
- A platform project is recognized by the
VendorplusNamepair of the manifest, not by theИдofПроект.yaml: a second project under the same pair is refused with 409ALREADY_EXISTS.
2026-07-25 – 0.14.0
Added
- The version suffix comes from the CI run number (
CI_PIPELINE_IID,GITHUB_RUN_NUMBER,BUILD_NUMBER) when there is neither an explicit version nor a previous build: a clean CI working directory no longer yields-1on every run. buildanddeploy --dry-runprint what was built -name,vendor,version,version-source,kind,branch,commitanddirty- so CI stops digging the version out of a file name.apps get/delete/start/stop/debugtake an exact application name as well as an id (in MCP too); several matches are an error listing the ids, destructive commands do not guess.apps list --brief- brief cards: the question “is acme-crm-dev alive” costs a few hundred bytes instead of tens of kilobytes.- Uncommitted changes in the project directory are visible in the report (
dirty,dirty-files), and--require-cleanstops the work before the build.
Changed
apps list --namefilters on the client: the platform ignores the query parameter, so the previous pass-through filter filtered nothing.- The keys of the
Проект.yamldescriptor are read in both spellings - bilingual sources are a declared feature of the platform, and such a project used to be refused. - Status waits treat
Erroras terminal: a deploy fails at once with the compilation errors instead of waiting out the full timeout.
Fixed
--lang enis English everywhere now. Some forty user-facing lines were hardcoded in Russian past the message catalog; a test walks the modules and keeps new ones from appearing.
2026-07-24 – 0.12.0, 0.13.0, 0.13.1
Fixed
- Files of
Ресурсыdirectories enter the archive whatever their extension. The general allow-list used to apply to them as well, files fell out of the build silently, and the apply failed with “Неизвестный ресурс”. Outside resource directories the list still applies, and it gained.htm.
Changed
- MCP:
list_projectsreturns brief cards by default - likelist_apps;brief=falsereturns the full ones.
Added
builds uploadreports its target (project-idandproject-id-source): an assembly used to land silently in the project from the environment when a new project was intended.builds upload --new-projectuploads the assembly as a new project, ignoringELEMENT_PROJECT_ID.builds uploadwarns about a foreign assembly name: the console shows a project under the name of the last uploaded assembly.- The command reference and its generator are under tests (
tests/test_cli_docs.py): every command answers--helpand has a section of its own, and the two language versions really do differ.
2026-07-22 – 0.11.0
Added
- The documentation site (docs.keyfire.ru/elemctl), a full command reference and CLI help – complete in English and Russian.
2026-07-21 – 0.10.0
Changed
- MCP: the environment is passed per call, and
apps listis brief by default. - The “no application source” error now tells you how to set a project up.
Fixed
- CLI help is localized: the language is resolved before the argument parser is built.
apps createsurfaces the task’s own compilation errors instead of the platform’s generic “Неизвестная ошибка. Обратитесь к администратору”.
2026-07-19 – 0.9.1
Changed
- The command section of the help is named “commands”, and the version is read from a single source.
2026-07-17 – 0.9.0
Added
inspect– parse a ready-made build archive.builds getandbuilds deleteaccept a build version and resolve it to the build id.
2026-07-15 – 0.5.0, 0.6.0, 0.7.0, 0.8.0
Added
self-update– update by unpacking the wheel, safe even when the running executable is locked (0.7.0).- A plugin system: the platform debug adapter is contributed through extension points (0.5.0).
Changed
verifyconfirms the deploy by the applied build’s id instead of the version string (0.8.0).- The MCP tool guidance notes that long operations are asynchronous – run the CLI in the background (0.5.0).
- PyPI project links (Homepage / Repository / Issues) in the package metadata (0.6.0).
Fixed
- The MCP server honours the global configuration arguments (
--env-fileand the rest) (0.7.0).
2026-07-12 – 0.4.0, 0.4.1
Added
- Full ru/en bilinguality: runtime i18n (
--lang/ELEMCTL_LANG), with the README and the specification in both languages. apps debug– the data for a platform debug session (POST /actions/debug).
Changed
- Releases publish to PyPI on a
v*tag through Trusted Publishing, with signed provenance attestations; thevscode-v*tags are left untouched.
2026-07-10 – 0.2.0, 0.2.1, 0.3.0
Added
- Initial release: a client for the documented Console API v2,
.xasm/.xlibbuilds from source, a one-command deploy with an honest check that the change actually applied, a CLI and an MCP server – written clean-room, to the specification indocs/SPEC.md(0.2.0). apps ensure– create the application only when it is absent (0.3.0).
Changed
apps findskips deleted applications;--include-deletedrestores the old behaviour (0.3.0).
Fixed
apps findexits 0 when the application is absent instead of failing (0.2.1).