Developer Workflows¶
Normative source-discovery and project-scoped commit behavior for trust-dev.
This document defines normative truST behavior for developer-facing source discovery and project-scoped Git commits. It does not define IEC 61131-3 language semantics.
Project initialization¶
When the product wizard initializes Git, an existing .git directory, a
symbolic link that resolves to a directory, or a valid worktree gitdir: file
means the project is already initialized. An ordinary or malformed .git
file, a worktree marker whose target is absent or not a directory, a dangling
or non-directory symbolic link, and any other filesystem object are errors;
the wizard must not silently report Git initialization success. Without an
existing marker, initialization requires an available git command and a
successful git init exit status.
The guided CLI, browser setup, and automatic first-run wizard must leave a
coherent runnable project rather than a collection of independently valid
files. A selected resource name and cycle interval are applied to
runtime.toml, the setup-owned src/config.st, and the rebuilt program image
program.tpi as one prepared change. The complete source set must compile before these
artifacts are replaced. A cycle interval of zero, an invalid or reserved IEC
identifier, or a source/build failure rejects the setup instead of writing a
known-invalid runtime configuration.
Names derived from a project folder are deterministic valid, non-reserved
Structured Text identifiers under docs/specs/01-lexical-elements.md. Empty or
punctuation-only names use Res; a leading digit or reserved keyword is
prefixed with Res. This generation rule is product scaffolding that consumes
the IEC identifier contract; it is not an IEC decision or deviation.
The wizard's build recursively discovers .st and .pou files using
ASCII case-insensitive extensions, deterministic path order, and literal
filesystem names. It includes resolved local project dependencies and
propagates directory/file-read failures. Selecting system I/O removes an
existing project io.toml regular file or symbolic link, including a dangling
link, but rejects a directory or unsupported filesystem object at that path.
Automatic setup migrates a legacy sources/ tree to src/ before discovery,
removes only the exact obsolete generated io.st template, and compiles the
migrated project plus resolved local dependencies into a program image that
this computer admits (specification 44 §10.2).
Remote browser setup tokens contain 128 bits from the operating-system secure
random source. If that source fails, setup aborts before binding the server; it
must not use zero bytes, timestamps, process IDs, or another fallback token.
The configured minute TTL is converted and added to the current Unix time with
checked arithmetic. Overflow is a setup error rather than a wrapped or
effectively unbounded credential lifetime. Local loopback setup remains
token-free; remote setup accepts the unexpired token from X-Setup-Token or
the token query parameter.
Guided setup modes have explicit side-effect boundaries. Browser and CLI dry runs report their effective project, access, and device defaults without creating project or system files or binding a server. System-I/O setup flags cannot be combined with guided project options. A non-interactive invocation without an explicit mode fails with guidance rather than waiting for input. Browser-only access, bind, port, and token-TTL options are rejected for CLI mode instead of being accepted and silently ignored, including when an explicit value equals the browser default.
The browser defaults response and a partial apply request use the same safe
Boolean defaults: use system I/O is true, write system I/O is false, and
overwrite system I/O is false. Consequently an apply request must explicitly
set write_system_io to true before it can attempt a system-wide I/O write;
omitting that optional field must never acquire the side effect. Project data
and apply routes require the configured unexpired setup token in remote mode.
These setup-mode, HTTP API, and filesystem-side-effect rules are truST product
contracts, not IEC decisions or deviations.
Executable tutorial examples¶
The first eight single-file programs under examples/tutorials/ and the
standalone examples/tutorials/09_simulation_coupling/ project are shipped
product artifacts. Every Structured Text source must build into a program image
for this computer through the in-process test harness (specification 10
§11.1). Tutorial 09
must also pass trust-runtime check --project from its checked-in project root,
including its runtime and I/O configuration, so the documented Runtime Panel
flow works without repository-root fallback. Success requires these paths to
return without a compile, type-check, lowering, or validation error; this
bounded fixture set does not certify arbitrary Structured Text programs.
Three tutorials additionally define executable behavior locks:
02_blinker.ststarts withLamp = FALSE. After 250 ms and one cycle it isTRUE, remainsTRUEafter another 1 ms and cycle, in which its timer is off, and after another 250 ms and cycle, whose rising edge starts the timer again atETT#0s (specification 08 §5), and returns toFALSEafter another 250 ms and cycle.03_traffic_light.ststarts red and advances through red+yellow, green, yellow, and back to red, each phase observed after 500 ms and a cycle that moves it on, a 1 ms cycle that shows it while the timer is off, and a 1 ms cycle whose rising edge starts the timer again atETT#0s.05_motor_starter.ststarts stopped, latchesMotorRunandSealInContactafterStartPb, remains latched whenStartPbis released, unlatches onStopPb, can start again, and unlatches onOverloadTrip.
Every observed cycle in these behavior locks must complete without runtime errors. The exact delays and I/O sequences above describe the shipped tutorial fixtures, not general IEC timer, traffic-control, or motor-safety semantics. They are truST product contracts and are neither IEC decisions nor IEC deviations.
Shipped project example corpus¶
The reviewed shipped project corpus is a product artifact rather than an arbitrary-language conformance claim:
- every communication example directory contains its required project,
runtime, I/O, and source files and succeeds through
trust-runtime buildfollowed bytrust-runtime validate; - the plant demo configuration retains its reviewed resource, task, program, and directly represented I/O bindings;
- the reviewed EtherCAT, Mitsubishi GX Works3, PLCopen ST-complete, and Siemens SCL project examples parse, type-check, and lower;
- every visual example companion/runtime pair builds into a program image for this computer, and at least one such pair exists; and
- every tracked Structured Text example in the repository example corpus parses without diagnostics.
Fixture discovery, copying, and filesystem setup are test preconditions. The product oracle is the bounded tracked corpus and the named build, validation, parse, type-check, and program-image outcomes; it does not certify arbitrary vendor projects or every IEC language partition.
Source Discovery¶
trust-dev test recursively discovers regular files below the selected project
root whose final extension is .st or .pou, compared with ASCII
case-insensitivity. Every ASCII case spelling of those extensions is supported.
Directory and file names are treated literally, including spaces, Unicode, and
glob metacharacters.
Unsupported extensions are excluded. When no supported source is found, the
command must report that the supported extension set is .st and .pou; it
must not report a successful empty test run. An unreadable supported source or
an unsupported discovery shape fails visibly rather than disappearing from the
result.
Test POU discovery¶
After loading supported source files, trust-dev test discovers only truST
TEST_PROGRAM and TEST_FUNCTION_BLOCK declarations. Discovery reports the
test kind, namespace-qualified name, source path, one-based declaration line,
and declaration source line. Comments and other parser trivia following a test
name do not change its identity. Results are ordered by source path, declaration
offset, and name so repeated discovery of the same source set is deterministic.
One implementation (trust_runtime::test_discovery) serves trust-dev test and
the VS Code Testing view (specification 25 §11.1), so a test has one name in both.
TEST_PROGRAM and TEST_FUNCTION_BLOCK are truST test-workflow extensions,
not additional IEC 61131-3 POU kinds and not IEC deviations.
They are executable entry points for the test runner. Unused-POU analysis must
not report W009 for either declaration merely because no production program
instance or function-block variable references it. Unreferenced production
POUs and unused local variables inside tests retain their ordinary warnings.
Test case execution¶
trust-dev test prepares one compiled runtime for the selected source set and
cold-restarts it before every selected case. The cold restart restores declared
initial values, so state written by one test case is not inherited by the next.
Shared discovery, compilation, and runtime preparation occur outside the
per-case execution deadline; that deadline begins immediately before the
selected case is invoked.
A matching name filter restricts execution and reported cases, not the source set needed to compile them. Register every discovered test program during runtime preparation, including filtered-out declarations in a configured project. Only matching cases are invoked. Human/CLI and structured JSON paths must obey the same rule; unrelated test programs must not become unbound or execute merely because a sibling test was selected.
Execution mode also compiles the complete project when discovery finds no test
POU. An invalid project therefore fails visibly instead of being reported as a
successful empty test run. Listing remains discovery-only, and an explicit
filter that matches none of the discovered tests does not execute or compile a
test case.
The selected declaration is then invoked at its own boundary:
TEST_PROGRAM as a program and TEST_FUNCTION_BLOCK as a function-block
instance.
The tests run compiled for this computer (specification 44 §14.2), in one
program image that holds every discovered test program as an instance no task
runs, as a debug or a release image as the project's [build] debug chooses
(specification 44 §13.2). They need the toolchain of specification 44 §4.6,
named by TRUST_TOOLCHAIN or installed; without it the command fails with the
advice to install it. A project with a construct the native build does not
compile yet fails with the build's diagnostic. With --device <port> --target <target> they run
on that device instead: the same image, compiled for the target, is installed
once over the device's control link, and each selected test runs by one
cycle.step that restarts the program cold and runs its test program's body
(specification 11 §6.8.1). The image runs no test program by itself: its test
programs are instances that no task runs, the background programs' included
(specification 44 §5.6), so a start of the device, at a reset for example, runs
only the project's own programs, and a test runs only when it is invoked. The
device's limits are its own, its statement budget among them. A failed assertion's error, assertion failed: <message>,
is the test's failure with that message, and any other error is the test's
error, as on this computer.
When a production CONFIGURATION exists, discovered test programs are registered
as additional program instances for the test session. A case filter selects
invocation and reporting, not which declarations participate in whole-project
compilation; unselected tests are not executed. A production build of the same
project (trust-runtime build, run, a debug or Control Studio session) leaves
its TEST_PROGRAM declarations unbound and does not instantiate them: they are
test entry points, not part of the production program, so a runnable project
may keep its tests beside its configuration. An unbound ordinary PROGRAM
under a CONFIGURATION is still refused. The test runner does not silently
alter the production configuration.
The hosted explicit entry APIs select admitted POU identities: a supplied program declaration contributes its name, never a replacement executable AST. Named test-function-block execution constructs a fresh instance from the admitted template and executable initializer recipes, within one temporary ownership scope. The common dispatcher runs the body with the same cumulative work/deadline budget; all scope-owned instances and frames retire on success or failure. This scope is not an extra PLC call frame, deployment root or persistent global binding. Live references to its instances cannot escape to longer-lived PLC storage. The test runner constructs the compiled installation once and cold-restarts it per case; it does not compile and install the identical application a second time.
An ASSERT_* failure is a failed test. A non-assertion runtime failure is an
error. A configured per-case deadline that is reached produces the distinct
execution-timeout result. Successful execution therefore requires entering the
selected test body; merely compiling or locating it is not success.
These runner and assertion behaviors are truST product extensions. They do not change IEC 61131-3 program execution outside the developer test workflow.
Test reporting and CI output¶
The test command preserves three result states: passed, failed, and
error. JSON output is version 1 and reports aggregate totals, total duration,
and per-test status, source context, and duration. TAP output uses a version-13
plan and reports each test by TEST_PROGRAM::<name> or
TEST_FUNCTION_BLOCK::<name>, with source diagnostics for non-passing cases.
JUnit output uses one compatibility suite named trust-runtime, reports
separate failure and error counts, and XML-escapes diagnostic text.
Human output identifies every non-passing test with its kind, name, source path, line, duration, reason, and available source line, followed by an ordered failure summary and aggregate counts. A filter that removes every discovered test reports both the filter and the unfiltered discovery count. List mode reports kind, name, project-relative path, line, and final listed count.
--ci changes the default human selection to JUnit. An explicitly requested
JSON, TAP, or JUnit format is preserved. Timeout prose uses singular
1 second and plural <n> seconds.
Native runtime CI fixture compatibility¶
The runtime compatibility entrypoint preserves the following reviewed native project-fixture contract:
- successful
build --ciemits version 1, commandbuild, statusok, the selected project path, and a source count of at least the two fixture source files; - successful
validate --ciemits version 1, commandvalidate, statusok, the selected project path, and resourceRes; - successful
test --ci --output jsonreports the selected project, zero failures and errors, numeric aggregate and per-case durations, at least one discovered test, and theCI_Passescase; - the green fixture's explicit JUnit run contains a test suite with zero failures; and
- the broken fixture's explicit JUnit run returns process class 12, contains a
failure entry, and reports
ST test(s) failedon stderr.
These are bounded compatibility fixtures. They do not certify arbitrary projects, every JSON field, XML schema conformance, or stable diagnostic prose beyond the named failure marker.
Change and release workflow¶
Owner decision, 31 October 2026: "i want the most professional best workflow".
Work reaches main in small pull requests, and CI is the only gate:
- one lane of work is one pull request of a few hours, pushed with its pull request from its first commit; the automatic review (Codex) runs when the pull request opens and on request after later pushes, and its findings are fixed in the same pull request;
- before each push the author runs the compile check of the changed crates and
the focused tests of what changed and of what uses it; the full suites
(
just test-all, the VS Code suites, the documentation checks and, for a version change, the release rehearsal) run in CI on every push; - the acceptance journeys of the shipped user paths (the PID walkthrough, Identify from Auto, the walkthrough's experiments and F23's runtime connection) run in CI on the packaged extension: on the linux-x64 package that a pull request changing the packages builds, and on the rehearsal's and the release's package before anything is published (specification 24, "Release rehearsal on pull requests");
- a pull request merges at its exact head when every required check is green
and the review has no open finding. There is no merge queue (owner, 1 November
2026: the repository stays under the personal account, and GitHub gives the
queue only to organizations), and the pull request need not hold the newest
main:main's CI checks every merge, a redmainis fixed before any other merge, and a release comes only from amaincommit whose CI passed or whose tree a passed rehearsal built and tested; - a user-visible change adds a changelog fragment,
changelog.d/<topic>.md, with Keep a Changelog sections and their entries, and leaves## [Unreleased]inCHANGELOG.mdempty, so that open pull requests never conflict over the changelog. A released section is its GitHub release's text and never changes: an entry written before a release, appended to the old## [Unreleased], would otherwise merge cleanly into the section the release made of it.scripts/changelog_fragments.py checkruns in CI and compares the released sections with the pull request's base; - a release is a pull request that only bumps the version and moves the
changelog:
scripts/bump_version.pygathers the fragments into the version's section, the newest first; it merges like any other. When the merge commit has the tree of the head its rehearsal built and tested, the release publishes that rehearsal's packages as soon asmainhas the commit; otherwise it followsmain's CI and builds them again (specification 24, "Release after main CI"). Such a pull request changes no behavior of its own, and both bug-fix checks judge it so: when the workspace version moves and every changed file is one the bump changes, the direct-change contract needs no companion and the regression-test-first check asks for no test, although its description names the fixes it releases; - release blockers are regressions and severe bugs (safety, data loss, a broken user journey); any other finding goes on the checklist for the next release.
The pre-push hook refuses only a direct push to main and a pushed release
tag; every other push goes through. Until 31 October 2026 a release guard
(release_candidate_guard.py prepare) ran the checks on the builder before a
push. CI runs every stage it ran, and git diff --check, and branch protection
refuses the merges its check-merge refused, so their code was removed
(5 November 2026). release_candidate_guard.py keeps the pre-push check,
collect-failures (every failed job log of a pull request), verify-release
and the post-merge audit.
Acceptance journeys on the packaged extension¶
The acceptance journeys prove the shipped user paths on the package that a
user installs, not on the development build that the VS Code suites test.
editors/vscode/src/acceptance/runAcceptance.ts (npm run acceptance --
--vsix <file> --out <folder>, under a display) installs the VSIX into an empty
extensions folder of a VS Code that has never run truST (the stable release,
or the executable that E2E_VSCODE names) and runs each session in its own
window, with its own profile and Control Studio state folder, one truST data
folder that is empty at the start, and no Rust toolchain: the runner removes
Cargo and rustup from PATH and fails when a Rust compiler is still found.
Each session copies its project from the checkout's examples/ and runs at
1×:
pid-commissioning: the walkthrough ofcontrol-pid-commissioningon the unmodified example: Start, Identify plant, Tune PI to the README's requirements, Validate, Apply with the response read from Live Values, and Restore previous settings (specification 35, B3 to B7);pid-try: the walkthrough's settling times of 6 s and 30 s;pid-slow: the slower heater,time_constant_s8.0;identify-auto-rest: Identify from Auto waits for the loop to settle before the transfer (specification 35, B3.4 step 5); the copy starts in Auto at a 30 °C setpoint;f23-localandf23-tcp: the tank example's runtime, built and started with the installedtrust-runtimeas its README says, on the default control endpoint and on a TCP endpoint with a control token generated for the run; the Target picker offers it, connects to it, and shows it stopped (specification 25, section 5.1).
Each step records its outcome, the question and notifications on screen, the
truST items of the status bar and a screenshot in <folder>/<session>/. A
step that fails is recorded and the journey goes on, so that one run shows
every failing step, and the session fails. Every session runs,
<folder>/summary.json lists each result, and the run fails when a session
failed. A session whose edit of its copy finds the example changed, so that
the text it replaces is not there exactly once, fails instead of running on
the unmodified project.
CI reliability and release-gate artifact contract¶
The reviewed native CI support surfaces preserve the following executable contract:
- the flake probe emits schema version 1, the selected project, exactly the
requested run count, pass and failure totals whose sum equals that count, a
numeric flake rate, and one ordered
passorfailsample per run; - the nightly reliability workflow remains manually dispatchable, runs the load, soak, and ST flake probes, samples 20 ST runs with zero accepted failures, enforces the reliability summary gates, and uploads run-scoped machine-readable artifacts;
- release-gate aggregation fails with verdict
FAILand the exact missing required artifact identities when any required gate artifact is absent, and returns verdictPASSwith an empty missing set when all required artifacts and successful gate results are present; - reliability-summary enforcement returns nonzero when a configured load, soak, or flake budget is breached;
- the public native CI template builds both CLIs, then performs runtime build, validate, JUnit test, and report upload in the reviewed order; and
- the VS Code extension job remains a required release-gate input, so a failed or absent extension artifact cannot be aggregated as release-ready.
These checks prove the named scripts, workflow wiring, machine-readable fields, and fail-closed aggregation boundaries. They do not prove that an unexecuted GitHub workflow ran successfully, that uploaded artifacts are publicly available, or that the final release candidate is green.
Each native test job builds every crate with all its targets and warnings
denied (RUSTFLAGS=-Dwarnings for the whole job), in the one build its tests
use, so a warning on any of the three systems fails the job. Until 4 November
2026 a separate step checked only trust-runtime that way, and its other
compiler flags compiled the crate and its dependencies a second time: 1.5, 3.5
and 14 minutes on Linux, macOS and Windows.
Compiler warnings are denied on each system, but Clippy runs only in the Linux
Clippy job, which never compiles code that only another system compiles. Its
cross-target gate (check_runtime_cross_target_warnings.sh) therefore runs
Clippy with warnings denied for x86_64-pc-windows-gnu, with default features
and all targets, on the crates with Windows-only code: trust-runtime,
trust-tcads-native, trust-control, trust-hir and trust-lsp (W40,
5 November 2026: a Windows-only needless_return in trust-tcads-native,
reported on #254, and three Windows-only lints in trust-runtime had reached
main).
The mesh TLS stability gate (runtime_mesh_tls_stability_gate.sh, four runs on
Linux and macOS) is the job's last cargo step, after the full suite and its doc
tests. Its cargo test -p trust-runtime --lib builds its own variant of
trust-runtime and some of its dependencies, and run before the full suite it
made the suite compile trust-wasm-analysis, trust-runtime and the five
crates that use them again: 14.5 minutes on Linux and 21 on macOS (main,
4 November 2026).
The gate repeats the mesh TLS tests named by three filters:
- the certificate requirement (mesh::tests::mesh_tls);
- the certificate a socket presents (mesh::security_tests::mesh_tls);
- the two-peer TLS exchange (mesh::hmi_remote_tests::two_tls_peers).
A run that passes without a passing test of every filter fails the gate. Until 5 November 2026 its filter named a test deleted long before, so every run reported "0 passed" and the gate passed without testing anything.
The full test suite runs under cargo-nextest with the ci profile of
.config/nextest.toml on every system: every test in its own process, and the tests of
all programs in parallel. A test that asserts how long something takes runs alone: the
profile names it and gives it every test thread, so that no other test runs beside it.
Under cargo test, which runs one program at a time, the test holds its program's
measurement lock alone while the program's other tests share it, or it runs in a test
program of its own. The build of every target before it has 30 minutes on Linux, 45 on macOS
and an hour on Windows, and Linux builds with two jobs, which its 16 GB runner holds (one job
took 22 minutes and ran out of the 30 in a third of the runs of 6 November 2026, two jobs 14).
The suite has two hours on macOS and Windows and one hour on Linux,
which CI_FULL_TEST_TIMEOUT_SECONDS may change: their runners are slower, and a macOS
runner took longer than an hour (28 October 2026). An attempt that exceeds its limit
fails.
A test that expects no watchdog fault from real cycles against a short watchdog runs alone too, as the debugger's pause tests do with their 20 ms watchdog.
Every job that builds Structured Text programs provisions the toolchain of
specification 44 §4.6 itself and names it in TRUST_TOOLCHAIN, the release's
self-hosted OpenOT database matrix and the documentation captures included; a
capture that runs in a container is given the same toolchain. A job never
relies on a toolchain left on its runner, so that a release run shows what a
clean runner does, nor on downloading one, because a version is not published
while its pull request is checked. v0.25.0's database matrix had no toolchain
and failed, and so did the browser IDE's build in 0.25.1's documentation
captures (28 October 2026).
Shared-builder Cargo targets used by an active gate MUST hold a stable external
lease for the complete command lifetime. Generated-target cleanup MUST acquire
that same lease without waiting and MUST skip a target whose lease is held; a
globbed deletion of the shared target root is forbidden. Lease descriptors
MUST NOT be inherited by helper daemons or detached background children after
the invoked command exits. A command that intentionally leaves an asynchronous
target user running MUST arrange a separate lease for that user instead of
extending the caller's lease accidentally through descriptor inheritance. A
cross-target command MUST NOT inject host CC or CXX overrides into the
target build; target-specific compiler discovery remains owned by Cargo and
the cross-target gate. Host-only commands MAY pin the host C/C++ compilers. A
successful assertion run followed by a missing executable due to concurrent
target deletion is an infrastructure failure, never a green test result.
Pull-request checks and change detection¶
Every workflow that checks pull requests starts on every pull request; none
filters its trigger by path. Its first job, changes, compares the pull
request's head with its base over the full history (git diff --name-only
--no-renames <base>...<head>), with no limit on the number of changed files, and
names the areas the change touches. A moved file counts at its old and its new
path, so moving a page out of a watched tree still runs the checks that publish
it. A job that checks an area runs when that area changed
and is skipped otherwise. A push to main uses the same detection, with the
same list of paths per area, kept in .github/paths/, against the commit of
that workflow's newest passed run on a push to main, as GitHub lists the
runs: a newer push replaces a run that still waits (below), so comparing with
the push's previous commit would never compare the replaced push's changes
(4 November 2026). Without such a run, or when GitHub cannot list the runs,
every check runs. A workflow with a changes job ends with a conclusion job
that runs unless the run was cancelled, needs every other job, and fails unless
each succeeded or was skipped; a failed changes job therefore fails the
workflow instead of skipping its checks. GitHub's own path filters compare only the first 300 changed files, so
0.25.0's 10,252-file pull request ran no documentation workflow, and the site
broke on main (28 October 2026).
The merge queue is not enabled (above), but the workflows are ready for it, so
that it can be turned on if the repository moves to an organization. The queue
tests each pull request again as a merge group: the pull request merged onto
the current main, or onto the groups queued ahead of it.
Every workflow that checks pull requests runs for a merge group (event
merge_group), and every job and step that runs for a pull request runs for a
merge group too. The detection compares the group's commit with the commit it
was built on (<base>..<head>), so the queue runs the checks of what the queued
pull requests change, not those of main's own commits. A merge group neither
rehearses nor releases: its pull request was rehearsed, and the release follows
main's CI (specification 24, "Release after main CI"). The project's own
runner runs neither a pull request nor a merge group, which carries a pull
request's code. The checks that main requires before a merge are listed in
.github/required-checks.txt; each is the name of exactly one job, in a workflow
that runs on pull requests and in the merge queue, so that a renamed job cannot
block every pull request and a shared name cannot satisfy a check for another
workflow (Render Diagrams and Docs Captures both named their last job
Conclusion until 1 November 2026).
A new push to a pull request cancels the pull request's unfinished run of
each workflow, so that no runner works on a head that nobody will merge; the
run of a merge group and a scheduled or manual run are never cancelled by
another (1 November 2026: three pushes to #151 ran three full CI runs side by
side while main's CI, which the release waits for, queued for runners). CI,
Render Diagrams and Docs Captures run the pushes to main one at a time: the
running run finishes, and a newer push replaces a run that still waits, so a
series of merges is checked twice, not once per merge (1 November 2026: six
merges in two hours queued about 120 CI jobs ahead of the release pull
request). The newest run checks the code of the merges it replaced, and the
release follows the passed run of any commit whose version has no release yet
(specification 24, "Release after main CI"). Docs Pages' deploys of main
share one group the same way, so the site skips the commits between (GitHub's
Pages workflows do the same).
Documentation that follows the product¶
The documentation is tested against the product, not only for its links,
assets, navigation and search. Every code block fenced as Structured Text in
the README, the pages of docs/public and the examples' READMEs quotes the
sources of its example, or of an example its page links to, each line without
comments and with its whitespace collapsed (a line or a part of a line written
as ... stands for code the quote leaves out), or else parses as Structured
Text; a fence marked ignore is left out. Every command that such a page or
the Marketplace page (editors/vscode/README.md) names by its Command Palette
title in bold, or as the title that a palette step
(Ctrl/Cmd+Shift+P and an arrow) types, and every setting it names by its key
in backticks, is one the VS Code extension contributes. No page names a name
the extension retired: the command category Structured Text:, in bold or
backticks, and the Runtime Panel or Runtime pane, which Live Values and the
truST view replaced. Every trust-runtime command line in a fenced block
parses with the runtime's command line. A quote that its program no longer
holds, a renamed or retired command or setting, and a renamed flag therefore
fail a pull request instead of leaving the page behind (30 October 2026: five
blocks that no parser accepted, among them an OpenOT variable named with the
keyword ACTION, three command titles that no longer existed, two command
lines that the runtime refused, ctl restart without its mode and a run
without its command, and 15 example and docs pages that sent readers to the
Runtime Panel or to Structured Text: commands, such as the six ADS commands
that Devices & Connections replaced).
Native test and docs-capture lifecycle portability¶
Native Unix control-endpoint fixtures must derive a collision-resistant socket
path that stays within the portable 100-byte pathname budget even when the
configured temporary directory is longer. A long TMPDIR must not turn an
otherwise valid runtime test into a socket-bind setup failure.
A test's temporary directory is its own. Its name holds the process id and a counter, not a clock stamp alone, because tests run at once in one process and in several, and two tests given one directory remove it under each other. A stamp may follow them, so that a directory an earlier run with the same process id left is not reused.
The automated docs-capture launcher owns every browser, runtime, and Docker process that it starts. On success, command failure, interruption, or timeout, it must terminate the complete owned process session and remove the named code-server container before returning. It must not terminate a reused or externally managed server. The executable lifecycle tests use fake commands and processes to prove cleanup ownership; rendered Playwright journeys separately prove the captured product surfaces. Lifecycle assertion deadlines must exceed the launcher's bounded graceful-termination window so the KILL fallback and process reaping can complete before the test runner times out.
Docs Captures¶
The Docs Captures workflow must run for pull-request and main-push changes to
the runtime Web UI source tree so browser-visible runtime changes cannot bypass
their registered Playwright journeys. Its change detection uses one list of
paths for pull requests and pushes to main. The Marketplace listing capture waits for the extension's
display name and publisher, which hold across releases, not for text of the
listing's README, which each release may change: 0.25.1's listing no longer
said "truST LSP", and the capture that waited for it failed every run (29 October
2026).
The documentation site shows the screenshots of main without a commit of the
images. The workflow runs on pull requests, on pushes to main, every night and
by hand, and none of its jobs writes to the repository. It marks the time before
its captures and uploads only the .png and .gif files written after that mark,
as the artifact docs-captures, kept for 30 days. A run whose captures write
nothing fails. Images that nobody renders, such as the brand and the hand-made
pictures, never enter the artifact, so laying an older artifact over a newer
commit cannot put back an older one of them.
The Docs Pages deploy starts on a push to main that changes the site, and
after each Docs Captures run of main that passed, that a push, the schedule or
a dispatch started, and that uploaded the artifact. Before it builds the site, it
lays over docs/public/assets/images the artifact of the newest such run. A pull
request's run never counts, so no pull request changes the published site. When
no such run exists, the images in git stay. They are the fallback: a change that
adds a capture commits its first image, and nobody commits refreshed ones. The
refresh pull request of earlier releases, which had to be merged into each
release by hand, is gone.
A pull request or a merge group that changes the site's paths runs the Docs
Pages build too, with the newest screenshots of main: the media, the link,
catalogue and database example checks, mkdocs build --strict, and the asset
and search checks. It publishes nothing, and its run never cancels a deploy of
main. Every job but the deploy runs with a read-only token, since a pull
request's run executes the pull request's own scripts; only the deploy may write
to Pages and request an OIDC token. Its conclusion, Docs Pages Conclusion, fails
when a check failed, so a broken page fails the pull request instead of the
deploy after the merge (the retired release guard ran these checks before each
push; Codex's review of #153, 1 November 2026).
Test-discovery parity¶
CI's MP-001 Parity job (scripts/check_mp001_split_parity.sh --verify) compares
the tests that trust-lsp's handler tests and trust-hir's
semantic_type_checking list, and trust-lsp's handler snapshots, with the
checked-in baseline in tests/fixtures/mp001. It does not run test bodies.
Adding or moving a handler test updates the baseline alongside the test.
Builder target storage¶
A cold just test-all on the builder needs at least 80 GiB free on the
filesystem that holds its generated target; moving a target to a mounted volume
does not lower that. Approved targets are strict descendants of the home
Cargo-target cache, /tmp, or
/mnt/HC_Volume_107089260/builder-storage/cargo-targets
(scripts/cargo_target_path.sh). The volume must actually be mounted. Target
paths cannot traverse . or .., alias another path through symlinks, or name
an existing target/parent owned by another user. The shared external lease and
idle-only reclamation apply to all approved roots, and policy is rechecked after
acquiring the lease.
A target migration holds both path leases, copies only the task-owned generated target, and verifies file bytes and symlink destinations before removing the original through the idle-target helper. Source checkouts, unrelated targets and validation evidence are not migration cleanup targets.
Post-merge release-candidate cleanup audit¶
The post-merge audit is read-only. It binds the reviewed candidate SHA, branch,
and main ref, then reports exact local branch, remote branch, clean worktree,
and prunable worktree-registration cleanup targets without deleting them. A
prunable candidate row whose directory is already absent is a
prunable_worktree target and must not be passed to git status or classified
as dirty. Unmerged candidates, divergent candidate refs, and existing dirty
candidate worktrees fail closed and suppress all cleanup targets until the
operator resolves the blocker.
Cargo integration-test binary-path portability¶
Workspace integration tests that launch a workspace binary must remain
compilable when Cargo checks or lints all targets without building that binary.
They must resolve Cargo's CARGO_BIN_EXE_* value from the execution environment
instead of using either compile-time env! form. This preserves ordinary
cargo test executable discovery while allowing the reviewed host and
cross-target warning and clippy gates to compile every crate's integration-test
targets. The CI portability scanner must inspect every Rust source below each
workspace crate's tests directory and reject compile-time lookup for every
binary name, not only trust-runtime.
CI performance reference-environment contract¶
The three native CI timing probes are reference-environment contracts, not portable performance guarantees. Their reviewed reference environment is:
- Ubuntu 26.04 on Linux x86-64;
- an AMD EPYC-Genoa Processor with 8 logical CPUs;
- 16,361,160,704 bytes of memory;
rustc 1.97.0 (2d8144b78 2026-07-07),cargo 1.97.0 (c980f4866 2026-06-30), andcargo-nextest 0.9.140 (a9fef2964 2026-07-05);- the already-built
trust-runtimetest binary and copied repository fixtures, with a warmed shared Cargo target; this is not an installation, clean-toolchain, or clean-compilation measurement; - the test runner's default thread selection unless the retained command says otherwise; and
- one observed wall-clock sample per assertion, no in-test warmup, and direct comparison with the asserted bound rather than a median or percentile.
On that profile, the first passing copied-fixture path must complete within 600 seconds; the filtered single-test path must complete within 2 seconds and report exactly one passing case with no errors; and the 10-test versus 40-test fixture pair must remain inside the asserted total-time, per-test, and scaling envelopes.
Every accepted execution artifact binds the source revision, exact test, hostname and profile above, command, terminal result, and observed duration. A result from different hardware, memory, operating system, toolchain, prebuilt state, cache state, concurrency, sample count, or aggregation policy is informative only and cannot satisfy this contract. These probes do not claim portable latency, clean setup, compilation speed, or arbitrary-project scaling.
Registry initialization CLI contract¶
trust-runtime registry init preserves the explicit root, visibility, and
optional token selected by the operator. --visibility private remains
private, and an explicitly supplied token reaches the private initialization
action without being reclassified as a public registry or another registry
subcommand. This parser contract does not establish storage authorization,
network authentication, or secret-handling proof beyond exact argument
binding.
Package registry storage and integrity contract¶
The local package registry is a fail-closed content store. Its configuration, index, package metadata, and package payload form one integrity boundary; a successful CLI message or the mere presence of a package directory is not publication proof.
Registry roots use schema version 1. Initialization creates
registry.toml, index.json, and packages/. A private registry requires a
non-empty normalized token before a usable configuration or index is written.
Reinitializing an existing registry may update its access configuration, but
it must first validate the existing index and must preserve every existing
package and index entry. An unreadable, malformed, or unsupported existing
configuration or index is an error and is never replaced with an empty
default.
Package names and versions are normalized by trimming surrounding whitespace.
The remaining value must be one non-empty path segment containing only ASCII
letters, digits, -, _, and .; the complete segments . and .. are
invalid. The same validation applies to publish, download, and verify
requests. Separators, traversal components, absolute paths, and symbolic-link
redirection must not escape the configured registry root. Registry payloads
contain regular files and directories only. A symbolic link or another
special filesystem entry in a source bundle or stored package is rejected
rather than followed or silently omitted.
A package identity is the normalized (name, version) pair. Published
identities are immutable: a second publish of the same pair fails without
changing its payload, metadata, or index. Publication validates access, the
bundle, its identity, and the existing index before creating an addressable
package. It stages the payload and metadata outside the final identity path,
then commits a complete package and its index entry. If any publication step
fails, the prior index remains byte-for-byte intact and no final package
identity is left behind. Temporary staging material is not listable and is
removed or safely recoverable before a later publication. JSON/config writes
use same-directory replacement so readers observe the old complete document
or the new complete document, never a truncated intermediate document.
metadata.json binds all of the following:
- the package name and version, which must equal the requested identity and containing path;
- the runtime resource name and bundle schema version;
- a publication timestamp;
- every regular payload file exactly once, using a
/-separated relative path in ascending lexical order; - each file's byte count and lowercase SHA-256 digest;
total_bytes, equal to the checked sum of all file byte counts; andpackage_sha256, computed deterministically from the ordered path, digest, and byte-count tuples.
Verification rejects missing, extra, reordered, duplicated, renamed, resized, or modified payload facts, inconsistent totals, an identity mismatch, an invalid digest encoding, malformed JSON, and unsupported registry schema versions. It does not repair metadata or the index. Download with verification validates the stored package before mutating the destination and validates the completed copy before reporting success. A preflight or stored-package verification failure leaves an existing empty destination empty; a non-empty destination is rejected without changing its contents. Download without the verification option is an explicit unchecked copy and supplies no integrity claim.
index.json contains schema version 1, a generation timestamp, and exactly
one summary for every committed package identity. Entries are ordered first by
name and then by version. Each summary agrees with its package metadata for
resource name, publication timestamp, total bytes, and package digest.
Malformed JSON, a wrong schema version, duplicate or unsorted identities, or a
summary that disagrees with stored metadata is an error. Listing does not
silently normalize, delete, or replace such an index.
Every private-registry operation, including list, publish, download, and verify, authenticates before reading or mutating package state. Missing, blank, or unequal tokens are unauthorized. Registry profiles and operation reports never return the configured token. These local filesystem contracts do not claim network transport authentication or multi-process transaction isolation.
PLCopen XML semantic import contract¶
PLCopen XML import is a typed translation into the supported truST Structured Text subset, not text scraping. XML element and attribute names used by the supported profile are matched case-insensitively. POU identity and declaration identity remain case-insensitive even when the source XML uses different spelling. The import report counts every discovered POU exactly once as either imported or skipped.
The supported POU kinds are program/prg, function/fun/fc, and
functionBlock/fb, ignoring ASCII case and punctuation in the kind spelling.
Unsupported or missing POU kinds, missing names, and graphical or otherwise
non-ST bodies are skipped with the stable named migration diagnostic; they are
never scraped into a source file. A supported POU containing both an ST body
and a non-ST executable body is also skipped rather than choosing one silently.
Benign body metadata such as documentation, comments, and addData may
accompany exactly one effective ST body.
An ST body that already contains the matching complete top-level declaration is preserved as the POU source. A body containing only executable statements is wrapped using the XML POU name, kind, and interface. A missing or empty body may produce a declaration shell only when the interface or supported vendor method metadata supplies meaningful declarations. Otherwise the POU is skipped.
The interface translation recognizes inputVars, outputVars, inOutVars,
externalVars, localVars, tempVars, globalVars, and accessVars, in
that stable order. Ordinary variables require one nonblank name and one
supported type, and may carry a simple initial value. Access variables require
an alias, instance path, type, and a direction mapped to READ_ONLY or
READ_WRITE. Section Boolean attributes map to CONSTANT, RETAIN,
NON_RETAIN, PERSISTENT, and NON_PERSISTENT. Mutually contradictory
modifiers, duplicate case-insensitive declaration names within a POU,
unsupported type shapes, invalid ST identifiers, or malformed required
metadata are loss-bearing errors: the POU is not published as apparently valid
ST, and a stable diagnostic names the rejected XML node and remediation.
Silently dropping a malformed declaration while importing the rest of its POU
is forbidden.
Supported type expressions include:
- all IEC elementary scalar tags in the declared profile;
STRINGandWSTRING, with an optional positive declared length;- nonblank
derivedtype names; - arrays with one or more complete inclusive dimensions and a supported base type;
- structures with at least one valid, uniquely named field;
- enumerations with at least one valid, uniquely named element and optional explicit values; and
- subranges with a supported base type and complete lower and upper bounds.
Multi-dimensional array bounds preserve source order. Structure fields preserve source order and simple initial values. An empty structure or enumeration, incomplete array dimension, nonpositive string length, missing derived name, or incomplete subrange is unsupported metadata rather than a fabricated type. Generated type and POU source must parse as the intended declaration kind before any source artifact is published.
Functions use interface/returnType when present, otherwise an importable
vendor plaintext header. When neither supplies a return type, the compatibility
profile deliberately defaults to INT and emits PLCO211. A synthesized
function with no real result assignment receives the compatibility
self-assignment and PLCO212; occurrences in comments or another identifier
do not suppress that repair.
Supported CODESYS method objects are imported only for function blocks.
Plaintext method headers preserve their declared visibility and return type.
When a method header is absent, import defaults it to METHOD PUBLIC, uses the
structured return type when present, and emits PLCO214. Interface sections
and the ST method body are preserved. A method already present
case-insensitively in the ST function-block source is not duplicated; missing
methods are inserted before END_FUNCTION_BLOCK in XML order. Method metadata
on another POU kind is skipped with PLCO213.
A PLCopen POU declared as program but referenced as a derived type is promoted
to FUNCTION_BLOCK with PLCO210, because a program cannot serve as an
instance type in ST. Import reports the resolved kind after promotion. All
fallbacks, promotions, skips, and unsupported metadata remain visible in the
migration report and semantic-loss accounting.
PLCopen configuration, resource, task, and program-instance translation¶
The importer recognizes configurations below
instances/configurations/configuration and directly below instances.
Direct resources below instances are wrapped in one deterministic
ImportedConfiguration. Element and supported attribute aliases are
case-insensitive. Each discovered configuration is counted exactly once and
produces at most one complete ST configuration source.
Configuration, resource, task, program-instance, target, type, and task
references must become valid ST identifiers. The compatibility translation
replaces invalid identifier characters with _, prefixes a leading digit with
_, and supplies the documented ImportedConfiguration, ImportedResource,
CPU, Task, Program, or MainProgram fallback only when the corresponding
optional source identity is absent. Every normalization is reported. Resulting
identities are unique case-insensitively within their ST scope; collisions
receive deterministic _2, _3, and later suffixes in XML order.
A task requires a nonblank identity and exactly one scheduling mode:
- cyclic tasks use
interval,cycle,cycleTime,period, or a nested interval value; - event tasks use
single,event, ortrigger.
An omitted mode defaults to INTERVAL := T#100ms. An omitted priority defaults
to 1. Explicit intervals accept valid IEC T#, TIME#, or LTIME#
durations, plus nonnegative ISO PT...S or PT...MS values translated without
overflow to an equivalent T# literal. Explicit priority must be an integer in
the runtime-supported task priority range. Empty, negative, nonfinite,
overflowing, or otherwise malformed schedule values are loss-bearing; they do
not enter generated ST. Supplying both cyclic and event modes is ambiguous and
rejects the task rather than silently preferring one.
A program binding requires a nonblank instance name and POU type. A nested
program below a task inherits that task when it has no explicit task
reference. An explicit reference must resolve case-insensitively to exactly one
task in the same configuration or resource; it is never silently redirected
to the first task. If a scope has program bindings but no task declarations,
the importer creates one AutoTask with T#100ms and priority 1, binds every
otherwise unbound program to it, and emits PLCO506 for a resource or
PLCO507 for configuration scope. Explicit unresolved references remain
errors and do not use this fallback.
Generated order is deterministic: configuration tasks, configuration program
bindings, then resources in XML order; within a resource, tasks precede
program bindings. Resource programs nested below a task retain their inherited
binding. The output closes every resource and configuration and must parse as
the intended ST project-model declarations before publication. An empty
instances container emits PLCO501; an empty normalized configuration emits
PLCO508. Neither condition is represented as scheduling proof.
PLCopen global-variable-list translation¶
Each globalVars element is one discovered list. The list identity comes from
its nonblank name or child name, otherwise the stable GlobalVarsN fallback.
Its imported filename and generated ST identity use the same path-safe and
ST-safe normalization rules, and case-insensitive list collisions receive
deterministic suffixes rather than overwriting or publishing duplicate
namespaces/configurations.
Interface-as-plaintext metadata takes precedence when present; otherwise the
importer synthesizes declarations from structured variable entries. A list
must contain at least one complete, parseable declaration. Names are
case-insensitively unique within the list; each declaration has a valid ST
identifier, supported type, and optional simple initial value. Malformed,
duplicate, or partially parsed declarations reject that list with a stable
diagnostic instead of being silently omitted. Section modifiers and leading
vendor attributes are preserved only when they produce valid ST.
In native vendor-parity mode, an ordinary list produces VAR_GLOBAL; a
qualified_only list produces one NAMESPACE <list> containing its
VAR_GLOBAL block so List.Member remains the access spelling. The marker is
recognized from plaintext attributes or structured CODESYS attribute metadata
case-insensitively.
In strict-IEC adapter mode, an ordinary list produces one deterministic
configuration-owned VAR_GLOBAL block. A qualified-only list produces a
<List>_TYPE structure plus a <List>_Globals configuration holding one
global <List> instance. POUs that reference List.Member receive the
corresponding VAR_EXTERNAL List : <List>_TYPE; declaration exactly once,
unless they already declare it case-insensitively. Injection preserves the
POU header and existing variable-section order and occurs before executable
statements. PLCO603 records every synthesized adapter.
List order, declaration order, initial values, modifiers, and generated paths are deterministic. Counts and written-source paths in the import report match the complete artifact set; a skipped list contributes semantic-loss evidence and never appears in the imported count.
PLCopen semantic export contract¶
PLCopen export first performs a read-only semantic preflight over the complete selected source set. Every source must be valid UTF-8 and must parse without ST syntax errors. A parser error, duplicate case-insensitive POU/type/GVL/ configuration identity, malformed supported declaration, or incomplete project-model binding blocks publication before XML, source-map, adapter, or SCL output is changed. Unsupported but well-formed top-level kinds may be skipped only with a source-path and physical-line warning and only when at least one supported declaration remains.
The supported POU projection is PROGRAM, FUNCTION, and
FUNCTION_BLOCK. TEST_PROGRAM and TEST_FUNCTION_BLOCK use the corresponding
standard PLCopen POU kind and retain a warning that the test marker is not
representable. POU bodies preserve normalized LF text and one trailing newline
inside CDATA. Names, kinds, source paths, and one-based physical declaration
lines are reproduced in the embedded and sidecar source maps. Source-map
entries follow the deterministic exported POU order.
TYPE blocks export elementary, string with and without a declared length, derived, multi-dimensional array, nonempty structure, nonempty enumeration, and subrange expressions through their matching PLCopen base-type representation. Field/element/dimension order and simple initial or explicit values are preserved. Invalid identifiers, empty structures/enumerations, incomplete bounds, invalid string lengths, duplicate field/element names, and unrepresentable expressions are not emitted as plausible XML. A valid but unsupported type may be skipped with an exact warning; malformed or duplicate authority blocks the export.
Each complete top-level VAR_GLOBAL ... END_VAR block becomes one global list.
The list identity derives deterministically from its project-relative source
stem and block ordinal. Leading attribute pragmas, section modifiers,
declaration order, comma-expanded identities, supported types, and simple
initial values are preserved in plaintext and structured metadata. Unterminated
or partially parsed blocks, invalid or duplicate declaration identities, and
unsupported types block export instead of silently shrinking the list.
Configuration extraction preserves complete CONFIGURATION, RESOURCE,
TASK, and PROGRAM bindings under the scheduling rules above. The exporter
normalizes supported interval literals but does not invent meaning for invalid
durations, priorities, ambiguous scheduling modes, missing resource ends, or
unresolved task references. Those are preflight failures. Deterministic XML
order is case-insensitive configuration name, then tasks, programs, and
resources; resource tasks and programs use the same ordering.
Function-block methods are exported in their owning POU metadata. Method identity is case-insensitively unique within the owner. Header visibility, return type, supported variable sections and modifiers, body, source identity, and declaration line are preserved. A malformed method or a method on an unrepresentable owner blocks its metadata rather than producing a partial method object.
The export report counters equal the XML nodes actually published. XML attribute text and CDATA are escaped without semantic change. Re-importing the generic output must recover the same supported declaration identities, POU kinds, type expressions, global declarations, configuration bindings, method order, and normalized ST bodies. Timestamp text is the only permitted nondeterministic semantic-export field.
PLCopen XML filesystem transaction and identity contract¶
PLCopen XML import and export are project migration operations, not IEC 61131-3 language semantics. An explicit library import or export call means the caller has already completed any required overwrite confirmation. The library still owns safe path resolution, complete artifact planning, and failure-atomic publication.
Import reads and parses the complete XML document and validates the project
root before creating a source directory or changing any project artifact. It
then plans the complete import result before publication: generated sources,
the optional preserved-vendor-extension file, and the migration report. Source
file and folder components are portable filesystem names. Parent components,
absolute paths, separators, empty components, trailing dots or spaces, and
platform device names cannot escape or make the result platform-dependent.
All generated paths remain below the selected project root, and generated
source paths remain below its src/ directory.
Generated source identities are compared with ASCII case-insensitivity against
both the complete planned batch and existing destination entries. A collision
uses the deterministic _2, _3, and subsequent suffix sequence; it never
silently replaces an existing source. Different input names that sanitize to
the same portable component remain distinct through that sequence. A
directory, symbolic link, or other special object at a planned source,
vendor-extension, report, or staging path is rejected rather than followed or
replaced.
A successful import publishes its complete planned artifact set as one user-visible transaction. If staging or publication fails, every pre-existing artifact is restored byte-for-byte and no partial source, vendor-extension, report, empty directory, or temporary artifact remains. Malformed XML, an invalid root element, an unreadable input, and other preflight failures are side-effect free. A structurally valid document with no importable ST content is the one diagnostic-only outcome: it publishes one complete migration report describing the skipped or unsupported content, returns the no-importable-content result, and creates no source or vendor-extension artifact.
Export recursively discovers regular .st and .pou files using ASCII
case-insensitive extension matching and deterministic project-relative path
order. Literal spaces, Unicode, and glob metacharacters in names do not change
identity. A matching directory, symbolic link, or other special filesystem
entry is rejected rather than read or followed. Case-insensitively duplicate
POU identities are ambiguous and reject export before output mutation.
The complete export artifact set is planned before publication. Generic export contains the XML document and source-map sidecar. A vendor target additionally contains its adapter report, and Siemens export additionally contains the complete SCL bundle. The XML embedded source map and sidecar represent the same profile, namespace, ordered POU identities, source paths, and one-based lines. An adapter report names the exact XML, source-map, and optional SCL artifacts committed by that export. Vendor-extension and JSON payloads are split safely across CDATA terminators so the resulting XML remains well formed and round-trips the original payload bytes.
Export stages every file and directory beside its final destination and then publishes the complete set. If any output, source-map, adapter-report, or SCL bundle step fails, all pre-existing artifacts remain byte-for-byte unchanged, no partial new artifact or staging path remains, and a symbolic-link output is never followed. On success, readers observe one coherent artifact generation. For identical project bytes, target, and output identity, export content is deterministic apart from the documented generation timestamp.
These guarantees are single-operation filesystem guarantees. They do not claim multi-process transaction isolation, crash-consistent durability beyond successful filesystem replacement, semantic equivalence for unsupported vendor constructs, or native vendor-project generation.
Developer CI exit classification¶
The trust-dev workbench uses the same stable CI failure classes as the runtime
entrypoint: invalid project or configuration input is 10, build or compile
failure is 11, ST assertion or runtime-test failure is 12, timeout is 13, and
an unclassified internal failure is 20. Matching is ASCII case-insensitive.
Timeout recognition precedes test, build, and configuration recognition. A
recognized class is not replaced by command context. Only an otherwise
unclassified message uses command fallback: build maps to 11, test to 12,
validate to 10, and any other or absent command to 20. The free-form
substring classifier is a compatibility boundary; it does not imply that
arbitrary prose containing one of its tokens is semantically typed.
API documentation generation¶
trust-dev docs uses an explicit project path when supplied. Without one, it
uses standard bundle detection and preserves a detection failure; it does not
invent the current directory as a project. Source discovery recursively
includes regular .st and .pou files under the resolved source root using
ASCII case-insensitive extension matching, deterministic project-relative path
order, and literal filesystem names. A matching directory, symbolic link, or
other special object is rejected rather than read or followed. An unreadable
or non-UTF-8 source and a source with parser diagnostics are generation errors.
The complete source set is read and parsed before the output directory or any
document artifact is changed.
The supported API declarations are PROGRAM, TEST_PROGRAM, FUNCTION,
FUNCTION_BLOCK, TEST_FUNCTION_BLOCK, CLASS, INTERFACE, METHOD, and
PROPERTY. Each item reports its exact declaration kind, namespace/class/
function-block/interface-qualified name, project-relative source path,
one-based declaration line, declared VAR_INPUT, VAR_OUTPUT, and
VAR_IN_OUT names in declaration order, and whether the declaration has a
return value. Ordinary local, temporary, external, access, and global
variables are not parameter metadata. Duplicate qualified names in different
source locations remain distinct source-qualified items.
A documentation block is the uninterrupted line- and/or block-comment sequence directly preceding a declaration. One line ending between the block and declaration is allowed. A blank line or any intervening non-comment token breaks association. Comment delimiters, conventional leading block-comment stars, and surrounding whitespace are removed while line order is preserved. An adjacent untagged line is an ordered detail line.
Recognized tags are ASCII case-insensitive @brief,
@param <name> <description>, and @return <description>. Non-tag lines after
a recognized tag continue that tag with one separating space until another tag
begins. Parameter-name matching is ASCII case-insensitive. A return tag is
valid for functions and typed methods or properties; it is invalid on a
non-returning declaration.
Missing tag descriptions, malformed parameter tags, a parameter absent from
the declaration, @return on a non-returning declaration, duplicate recognized
tags, duplicate parameter entries, and unknown tags produce visible
documentation diagnostics at the exact physical tag line. The first valid
@brief, @return, or case-insensitive parameter entry remains authoritative;
a duplicate is diagnosed and cannot replace or duplicate the first rendered
entry. The absence of a tag for a declaration or declared parameter is allowed.
Documentation-tag diagnostics are nonfatal quality findings. Generation succeeds only when every diagnostic is included in the selected documents and reported in the command summary. Source discovery, source decoding, parser, project resolution, output safety, and filesystem publication failures are fatal and must not publish documents.
Items are ordered by source path, declaration line, and qualified name.
Diagnostics retain source and physical-line order. Markdown and HTML contain
the stable document header, declaration identity, source location, return and
parameter metadata, tag content, and any diagnostic section. HTML escapes
&, <, >, double quotes, and apostrophes in every user-controlled name,
path, description, detail, and diagnostic. Markdown descriptions and details
remain author-controlled Markdown, while generated structural metadata remains
unambiguous.
The selected format owns a fixed artifact set: Markdown writes api.md, HTML
writes api.html, and both writes both. An unselected pre-existing artifact
is unchanged. The complete selected set is rendered and staged beside its
destination before publication. Output components and existing artifacts may
not be symbolic links or special filesystem objects. A successful invocation
atomically replaces the complete selected set and only then prints success. If
any selected artifact cannot be staged or published, all pre-existing output
bytes remain unchanged and no partial document, empty output directory, or
temporary artifact remains. A valid project with sources but no supported API
declarations produces a deterministic empty API index; a project with no
supported source files is an error.
These rules define documentation extraction and filesystem publication. They do not certify semantic correctness of authored prose, require documentation for every declaration, or provide multi-process output isolation.
Interactive numeric prompts¶
Numeric wizard/setup prompts accept unsigned base-10 values in the complete
u64 range. Negative values, non-decimal text, and overflow are errors that
name the prompt field; they are not clamped, wrapped, or replaced with the
default. An empty submitted line selects the displayed default.
Optional project resolution¶
Developer commands with an optional --project use the explicit path when it
is present. Otherwise they use standard bundle detection. If detection fails,
the command preserves that failure and must not substitute the current
directory as an invented project. This applies to package-registry publish and
other optional-project workflows, except when the owning specification
explicitly defines a first-run creation flow. The runtime launcher contract
defines that narrow exception for trust-runtime play.
HMI pages¶
HMI page generation and editing are specified in
docs/specs/39-hmi-builder.md.
Deprecated runtime workbench aliases¶
trust-runtime test, trust-runtime docs, trust-runtime commit, and
trust-runtime agent serve are compatibility aliases for the identically
named trust-dev workbench commands. Each alias preserves all selected paths,
text values, flags, timeouts, and output/format choices as operating-system
arguments. Test output values are human, junit, tap, or json; docs
format values are markdown, html, or both.
Before starting the child command, every alias writes a deprecation warning to standard error that names both commands and the removal-not-before date 2026-10-05. Removing an alias requires a separate behavior-change release.
The child executable is selected in this order: an explicit TRUST_DEV_BIN
path, an executable regular-file trust-dev sibling of the current
trust-runtime binary, then trust-dev from PATH. A directory,
non-executable file, or other unusable sibling must not shadow a usable PATH
command. An explicit but unlaunchable path fails with installation guidance;
it is not silently replaced by another executable.
The child inherits the alias process streams so interactive and machine
protocols remain usable. Child success returns success, a normal non-zero exit
status is propagated exactly, and on Unix signal termination maps to the
conventional 128 + signal status. A launch failure or a termination without
a representable status is an explicit command error rather than success.
Project-Scoped Commit¶
trust-dev commit --project <path> owns only the selected project path. Before
staging or committing, it must resolve that path canonically as an existing
directory contained by the repository; an unresolved or escaping scope fails
closed. It must inspect the existing Git index both before presenting the
summary and again after interactive prompts, immediately before mutation:
- a pre-staged path intersecting the selected project scope is a collision and aborts the operation before index or worktree mutation;
- a pre-staged path outside the selected project scope remains staged and is excluded from the project commit;
- when the selected project is the repository root, any pre-staged path is a collision;
- staged additions, modifications, deletions, renames, non-ASCII paths, and mixed staged/unstaged paths are all subject to the same intersection rule;
- cancellation and
--dry-rundo not mutate the index, worktree, or history.
The collision diagnostic must name the intersecting path. The helper never silently absorbs an existing staged change into the commit it creates, including one staged while the prompt is open.