Skip to content

Verified: Authoring for Logix (experimental)

Each claim the Authoring for Logix (experimental) page makes, with the tests that prove it in the last green CI run: 31 of 52 verified. The claims live in docs/claims/logix-authoring.yaml.

Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims

LGA-001 verified

The manifest tags a program names become controller-scope Logix tags carrying their initial values and descriptions; a tag no program names is not written, unless `target.logix` sets `all-tags: true`, and then every manifest tag with a type the subset carries is a controller tag, named or not.

where the docs say so · TestAllTagsWritesTagsNoProgramNames writes the same ladder and ST programs with all-tags off (the unnamed DINT, REAL and INT array are not written) and on (each is, typed, seeded and described from the manifest; the side code's heartbeat stays the side code's). An unnamed tag the subset cannot carry (a STRING, a LINT) is not written and refuses nothing, where the same tag named is refused. The CLI test reads all-tags from nautilus.yaml through both `naut logix deploy` (to a fake agent) and `naut logix write`.

  • pass TestManifestInitsAndDescriptionsLandOnControllerTags in logix/writer
  • pass TestImplicitTagsBecomeControllerTags in logix/writer
  • pass TestAllTagsWritesTagsNoProgramNames in logix/writer (3 of 3 passed)
  • pass TestAllTagsSkipsWhatLogixCannotCarry in logix/writer
  • pass TestLogixDeployAllTagsAndControllerScoped in cmd/naut

LGA-002 verified

`VAR_EXTERNAL` is optional: a ladder or ST program that names a manifest tag it did not declare still gets its controller tag, typed from the manifest. A manifest tag with no `role:` stops `naut logix deploy` and `naut logix write` with an error naming the tag.

where the docs say so · The CLI test deploys (to a fake agent) and writes a project whose ladder names four manifest tags it never declares: each becomes a controller tag typed from nautilus.yaml, `type:` where given (INT over the REAL `init: 0` would imply; DINT with no init) and else the type `init:` implies (REAL, BOOL); a manifest tag no rung names is not written. A tag with no role: is refused by name (exit 2) by `naut logix deploy`, before anything reaches the agent, and by `naut logix write`.

  • pass TestImplicitTagsBecomeControllerTags in logix/writer
  • pass TestImplicitTagsInST in logix/writer
  • pass TestImplicitTagsLadderMembersAndLiterals in logix/writer
  • pass TestLogixDeployTypesUndeclaredTagsFromTheManifest in cmd/naut
  • pass TestLogixDeployRefusesATagWithNoRole in cmd/naut

LGA-003 verified

The task becomes a periodic Logix task at the task's scan rate, the program a Logix program and its rungs the `MainRoutine`.

where the docs say so · The CLI test deploys a project (to a fake agent) and reads the L5X it sends: a PERIODIC task at the task's 250 ms scan scheduling the program, the program named for the POU with the rungs as MainRoutine and its VAR as program (not controller) tags, and with `program`, `routine` and `task` under target.logix the three renamed.

  • pass TestTaskSchedulesTheProgram in logix/writer
  • pass TestDemoLineMatchesTheRealExport in logix/writer
  • pass TestLogixDeployNamesTheTaskProgramAndRoutine in cmd/naut

LGA-004 verified

`side.heartbeat` adds a second Logix program, `Nautilus`, scheduled after the user's, that counts task scans in the named DINT and never touches the user's routine.

where the docs say so

LGA-005 verified

The Logix target takes one task running one ladder or structured text program; a multi-task project and FBD or SFC programs are refused.

where the docs say so · FBD and SFC programs are refused by `naut check --target logix` (an SFC chart that checks clean without the target); `naut logix deploy` refuses an SFC task program and a project with two tasks.

  • pass TestCheckTargetLogixRefusesOtherLanguages in cmd/naut
  • pass TestLogixTargetRefusesSFCAndASecondTask in cmd/naut (4 of 4 passed)

LGA-006 verified

Contacts `X` `/X`, branches (nesting kept) and coils `( X )` `( S X )` `( R X )` are written as `XIC` `XIO`, `[ , ]` and `OTE` `OTL` `OTU`.

where the docs say so

LGA-007 verified

Edge contacts and edge coils (`+X`, `-X`, `( P X )`, `( N X )`) are written as `ONS` and `OSF` with generated storage bits.

where the docs say so · `-X` (to `OSF`) is asserted only through the subset golden snapshot.

LGA-008 verified

Compares become the Logix compare instructions (an expression operand becomes `CMP`), and `{ y := expr }` becomes `MOVE`, `ADD`, `SUB`, `MUL` or `DIV` for one operation on plain operands, `ABS` for `ABS(x)`, and `CPT` for anything else (`MOD`, unary `-`, `SQRT` as `SQR`, `**` or `EXPT`, nested expressions).

where the docs say so · MOVE, ADD, SUB, MUL, DIV, ABS, CPT (including MOD, unary minus, SQRT, ** and EXPT) and CMP are asserted, and the plain compares GE, NE, GT, LT (golden snapshot) and LE.

LGA-009 verified

In ladder, `TON`, `TOF` and `CTU` blocks are written as the Logix instructions on `TIMER` / `COUNTER` tags; in ST, as `TONR(t)` (with `t.Reset := NOT IN`), `TOFR(t)` and `CTUD(c)` on `FBD_TIMER` / `FBD_COUNTER` tags. Either way `t.Q` is written as `t.DN`, `t.ET` as `t.ACC` and `c.CV` as `c.ACC`.

where the docs say so · Ladder `TOF` and the `TIMER` / `COUNTER` tag types are asserted through the subset golden snapshot; the ST TON and CTU forms and the `FBD_TIMER` / `FBD_COUNTER` types by TestStructuredText, ST `TOF` and `c.CV` by TestSTTOFAndCounterValue, ladder `c.CV` by TestOutputCapturesAndResetConditions.

LGA-010 verified

`Word.3` is written as bit 3 of the word, as Logix spells it.

where the docs say so

LGA-011 verified

An array of timers or counters (`ARRAY [0..3] OF TON`) becomes one Logix `TIMER` or `COUNTER` tag with the array's dimension (`TIMER[4]`).

where the docs say so

LGA-012 verified

TIME becomes a DINT of milliseconds where it feeds a preset, and the elementary types BOOL, SINT, INT, DINT, REAL, LREAL map to the same Logix types.

where the docs say so · The elementary types are asserted through the subset golden snapshot (BOOL, SINT, INT, DINT, REAL, LREAL tags).

LGA-013 verified

STRUCT types in a library become UDTs.

where the docs say so

LGA-014 verified

Enumerations become `DINT` tags, and a member, plain or qualified (`Run`, `Mode#Run`), is written as its integer value.

where the docs say so

LGA-015 verified

A user FUNCTION_BLOCK, in ladder or ST, becomes an Add-On Instruction.

where the docs say so

LGA-016 verified

An ST program's assignments, IF, CASE, FOR, WHILE, REPEAT, EXIT, operators and maths are written as Logix ST, close to as written.

where the docs say so · Assignments, IF/ELSIF, CASE, FOR, operators and ABS/SQRT/EXPT are asserted, and WHILE, REPEAT ... UNTIL and an EXIT inside an IF in the loop, in order, as written.

LGA-038 partly verified

An ST conversion is written as Logix that computes nautilus's result: a narrowing wraps in integer arithmetic, an integer meets REAL as `(v * 1.0)` where it would otherwise divide as integers, a BOOL becomes a number through an `IF`, and REAL to an integer as the whole right-hand side into a tag of that integer type is a plain store, which the controller rounds halves to even as nautilus does.

where the docs say so · The tests assert what is written and that the wrap equals nautilus's narrowing under either MOD sign rule. The conformance project `logix/writer/testdata/conformance/conversions` writes every written class into its own tag (and passes a conversion to a user block's input); its 12 scenarios pass on nautilus in CI (the three cited) and passed unchanged on an Echo v38 controller with `naut test --target logix` (#255, 2026-10-14), exact to the bit with float32-exact REAL inputs. The controller run is not in CI.

LGA-017 partly verified

What the target cannot express is a `naut check` diagnostic that names the construct, and the alternative where Logix has one, carries a stable `logix/<rule>` ID and a line, and stops the writer from emitting any document.

where the docs say so · Every rule has a case, including trailing-underscore names (logix/name) and an AOI with an array input or output (logix/type); ladder, ST and user-type refusals each assert a line and that no document is written. The alternative is asserted for twelve messages across logix/name, fb-pin, operand, time, member and array-shape, not for every message. The page names the exceptions with no alternative (STRING, a self-containing type, a binding the compiler cannot read).

  • pass TestRejections in logix/writer (31 of 31 passed)
  • pass TestStructuredTextRejections in logix/writer (9 of 9 passed)
  • pass TestUserDefinedTypeRejections in logix/writer (6 of 6 passed)
  • pass TestRefusalsTheTableNames in logix/writer (4 of 4 passed)
  • pass TestSTAndTypeRefusalsHaveALineAndWriteNothing in logix/writer (3 of 3 passed)
  • pass TestRefusalsNameTheAlternative in logix/writer (13 of 13 passed)

LGA-039 verified

`logix/conversion` refuses what the controller would compute differently: REAL to an integer inside an expression or into a tag of another type, an integer `/` whose quotient meets a REAL (stored into a REAL, beside a REAL operand, compared with a REAL), and unsigned, bit-string, LINT, LTIME and STRING conversions; an all-integer division is written unchanged. On a rung the same integer `/` is refused in a DIV or CPT into a REAL, a CPT beside a REAL and a CMP against a REAL; into an integer it is written as DIV or CPT.

where the docs say so · That Logix divides integers in REAL beside a REAL (7 / 2 = 3.5) was observed on an Echo v38 controller, in ST (#361) and in ladder DIV, CPT and CMP (#368), not in CI. The written forms (`conformance/division`: DIV and CPT into an integer, a CMP against an integer, a quotient through a DINT tag into a REAL) passed 4 of 4 scenarios on the same controller; CI runs them on nautilus only.

LGA-040 verified

In an ST call to a user block, each input must be a tag, a member or element of one, or a literal; an expression bound to an input is refused by `logix/fb-pin` at check time. A negative number is written as one (`-5`), and a conversion that is a plain store (`DINT_TO_REAL(a)` into a REAL input) is written as its argument.

where the docs say so · That the Logix SDK build refuses any expression as an Add-On Instruction operand in ST (`(a / b)`, `(a)`, `-a`; RxSTX_E_UNEXPECTED_TOKEN) while it takes `a`, `p.y`, `-5` and `2.5` was observed on the v38 SDK (#368), not in CI. An element with a tag index (`arr[k]`) is accepted by the writer but was not built.

  • pass TestSTBlockCallInputs in logix/writer (16 of 16 passed)

LGA-018 verified

A project with a `target: logix` section gets the Logix rules from `naut check` without asking, and `naut check --target logix` applies them to any project.

where the docs say so

  • pass TestCheckRunsTheManifestTarget in cmd/naut
  • pass TestCheckTargetLogix in cmd/naut

LGA-019 partly verified

`dialect: logix` adds a `TONR` block to every program in the project, with `Reset` clearing it while TRUE, and a free-running pulse spelled `t:TONR(PT := T#1S, Reset := t.Q)`; the default dialect adds nothing.

where the docs say so · Only that the library parses and declares. No test runs a TONR on the nautilus runtime to check the Reset semantics.

LGA-020 verified

In ladder the writer emits a `TONR` as a `TON` with a `RES(t)` rung ahead of the timer's rung; in ST, as `TONR(t);` on an `FBD_TIMER` whose `.Reset` is TRUE while `Reset` is TRUE or `IN` is FALSE.

where the docs say so

LGA-021 verified

A tag's `alias:` is written as a Logix alias tag (`AliasFor`) and the program names the tag; the nautilus runtime ignores the binding, so the same source runs in simulation.

where the docs say so · One project with `alias:` on its tags: `naut test` runs its seal-in scenario on the nautilus runtime, which ignores the binding, and the deploy (to a fake agent) writes those tags as alias tags (AliasFor) with the rung naming the tags.

  • pass TestAliasTags in logix/writer
  • pass TestAliasedTagsRunInSimulationAndDeployAsAliases in cmd/naut

LGA-052 partly verified

With `target.logix.hardware` set, the writer puts that L5X's `<Modules>` section into the project unchanged, in place of the generated controller module. An alias of a rack point whose module the configuration lacks (a slot of `Local` or of an adapter, or a module by name), or with no configuration at all, stops `naut logix write` and `naut logix deploy` with `logix/io-module` naming the alias and the module; a configuration whose `Local` module is not the target's processor at its revision stops them with `logix/hardware`. A project written with a configuration imports and builds in Logix Designer with its aliases resolved.

where the docs say so · The writer tests use a real Logix Designer v38.01 export with two 1756 modules in the controller's chassis and a 1734-AENT adapter with a 1734-IB8 under it (lang/l5x/testdata/ioline.L5X), in ladder and ST. TestSDKConvertAndBuild/ioline imports and builds the written project with the SDK; it runs only against a logixd (ws-test, #275), so CI does not run it. Measured by hand on ws-test (Logix Designer v38.01, Echo 5580, 2026-10-14): the same aliases with no modules import and then fail the build with RxTAG_ALIAS_TARGET_NOT_FOUND, the failure logix/io-module now reports at write time; the project with the hardware configuration downloads, verifies, runs in Run, and writing the input alias tags moves Local:1:I.Data and the seal-in drives Local:2:O.Data. The rule checks that the module is there, not that the point exists on it.

  • pass TestHardwareModulesWrittenVerbatim in logix/writer (3 of 3 passed)
  • pass TestIOModuleRule in logix/writer (11 of 11 passed)
  • pass TestHardwareMustMatchTheTarget in logix/writer (3 of 3 passed)
  • pass TestRackPoint in logix/writer
  • pass TestAliasedTagsRunInSimulationAndDeployAsAliases in cmd/naut
  • pass TestLogixImportHardwareRoundTrip in cmd/naut
  • skip TestSDKConvertAndBuild/ioline in logix/writer

LGA-022 verified

`naut logix import --project` writes a nautilus project from a full export: the manifest with `target: logix` filled from the controller (and `dialect: logix` when the export uses its idioms), the tags with their descriptions, UDTs as IEC types, every ladder routine as a PROGRAM and every AOI as a FUNCTION_BLOCK in `lib/`.

where the docs say so · TestEveryLadderRoutineBecomesAProgram imports an export with three ladder routines in two programs: three PROGRAM files, each a task in the manifest, each with its own rung; the manifest's target filled from the controller (name, processor, revision); controller-scope descriptions in the tag file.

LGA-053 verified

`naut logix import --project` of an export with I/O modules writes `hardware.L5X`, the export's `<Modules>` section unchanged, and names it in the manifest (`target.logix.hardware`), so the write-back carries the same modules with the alias tags on them; an export whose only module is the controller's own gets no hardware file.

where the docs say so · Line ends are made LF (XML reads CRLF the same); otherwise the section is the export's bytes. Measured by hand on ws-test (Logix Designer v38.01, 2026-10-14): the write-back of the imported ioline export, imported into Logix Designer and exported again, has a `<Modules>` section byte for byte the original export's.

LGA-023 verified

A rung with no nautilus form is kept as a comment and reported, never guessed at.

where the docs say so

LGA-024 verified

Alias tags and rack points the logic names directly become tags with `alias:`, and a `RES` on a timer run by `TON` imports as `dialect: logix` `TONR`. A rung that names an alias tag comes in like any other: the tag takes its target's type, whether the target is a tag, a member of a UDT or Add-On Instruction instance, an array element, a bit or a rack point's bit. An alias the export cannot type, such as one of a whole rack word with no module definition, is refused by name.

where the docs say so · Rack points named directly, the TONR fold, and an alias tag in the export becoming a tag with `alias:` are asserted. A hand-built export (logix/importer/testdata/aliases.L5X) has rungs naming an alias of each target kind (Pump1Run; Pump1.Speed; the BIT member Pump1.Running; the Add-On Instruction output Valve1Ctl.Open; TankStatus.5; TankLevels[2]; Local:1:I.Data.3): every rung imports, each tag is declared as its target's type (BOOL, REAL), and written back the rung text is the export's with each tag an alias tag of the same target (#330). Refused by name, each asserted: an alias of Local:2:I.Ch0Data ("the export has no module definition to type it"), of a tag not in the export, of a whole TIMER, of a member the target lacks, and a program-scoped alias tag.

LGA-044 verified

An imported project writes back with the export's tags where the export had them: a controller-scoped timer, counter or Add-On Instruction instance, declared in its program, is listed under `target.logix.controller-scoped` with its description and written back at controller scope, and with `all-tags: true` a controller tag no imported routine names is written back too, the tag file typing each tag. A tag with no nautilus type, or one the subset lacks, is reported. On the public yardstick (`examples/batch-skid/line/Line.L5X`) the logic comparison finds no differences.

where the docs say so · TestLogixRoundTripYardstickCLI runs import --project, naut check, naut logix write and normalize --logic --check on the public yardstick: "the same logic", every tag's value and description equal to the export's, AcceptTmr and Line_BatchId among the controller tags and no program tags. TestImportTypesEveryControllerTag asserts the tag file's types (a DINT, an array, a tag with no value) and the report for a MESSAGE, a multi-dimensional array, an unnamed TIMER and a LINT.

  • pass TestLogixRoundTripYardstickCLI in cmd/naut
  • pass TestRoundTripPublicYardstick in logix/importer
  • pass TestImportTypesEveryControllerTag in logix/importer
  • pass TestControllerScopedVarBecomesAControllerTag in logix/writer
  • pass TestControllerScopedAOIInstance in logix/writer
  • pass TestControllerScopedRefusesWhatIsNotAProgramVar in logix/writer
  • pass TestLogixDeployAllTagsAndControllerScoped in cmd/naut

LGA-045 partly verified

An import carries each UDT's description and its members' into the project, as the declarations' trailing comments, and a write puts them back on the `<DataType>` and its `<Member>`s; the written file claims the Logix Designer release of the target's own revision (36.00 for a v36 controller, 38.01 for v38).

where the docs say so · On the public yardstick the written SoftwareRevision is the export's (36.00) and LineStatus comes back with its description; member descriptions are covered by the writer's own tests, since the yardstick has none. Offline only: 38.01 is what ECHO1's real v38 exports carry; any other major's release is its ".00", which matches the yardstick (a stand-in export) for v36 but no real export has shown, and no v36 Logix Designer has opened a write-back.

LGA-026 not run in CI

On a corpus of 52 real production exports, 97.9% of rungs import, 81% of ladder routines import whole, and most complete routines write back identically or equivalently.

where the docs say so · The corpus is private (NAUTILUS_L5X_CORPUS), so the test skips in CI; the figure is not independently checkable.

LGA-027 partly verified

`naut logix deploy` with no flag writes the project, has logixd import and build it, and says whether the running controller could take the change as an online edit or needs a download.

where the docs say so · Run against a fake agent; no controller behind it in CI.

  • pass TestBuildOnlyReportsWhatTheControllerNeeds in logix/deploy
  • pass TestEmptyControllerNeedsADownload in logix/deploy
  • pass TestDeployBuildsAnAOIProjectInTwoSteps in logix/deploy

LGA-028 partly verified

`naut logix deploy --online` replaces the routine's rungs in the running program and finalizes them, leaving the controller's mode, and is refused when the tag set changed because an online edit cannot create tags.

where the docs say so · Run against a fake agent; real warm edits are verified on hardware only.

LGA-029 partly verified

`naut logix deploy --download` refuses without `--yes`, and the controller must already be in Program mode or `--program-mode` passed.

where the docs say so · The "controller must be in Program mode" refusal is not asserted separately; real downloads are verified on hardware only.

  • pass TestLogixDeployRefusals in cmd/naut (5 of 5 passed)
  • pass TestDownloadVerifies in logix/deploy

LGA-030 partly verified

Every deploy path ends by uploading the controller's program again and comparing its logic with what was sent.

where the docs say so · Verified against a fake agent that returns the sent logic.

LGA-047 partly verified

`naut logix deploy --download` keeps the controller's tag values: it uploads the running controller before the build and writes into the project the value of every tag with the same name, scope, type and dimensions on both sides (a UDT's or Add-On Instruction's definition the same too) that the project does not mark constant; a TIMER's or COUNTER's preset comes from the project, its accumulator from the controller. A tag whose type, dimensions or definition changed starts at the project's value and is reported, as are new and removed tags. `--reset-values` starts every tag at the project's value.

where the docs say so · The merge is tested against a real SDK upload of an Echo controller (testdata/running.L5X: REAL, DINT, a nested UDT, a REAL array, a TIMER and a COUNTER, controller and program scope) and, through the deploy, against the fake agent (the upload precedes the import; with ResetValues the project's values go). On an Echo controller (v38, 2026-10-14, #273) a changed project downloaded with every value kept, a DINT → REAL tag reset, a new tag at its initial value, a changed timer preset from the project and a counter's accumulator kept, and the controller ran the new logic from the kept values; the hardware test skips in CI.

LGA-031 verified

A program the target cannot express is refused before anything is written: `naut logix deploy` stops on the diagnostic before the agent is called.

where the docs say so

LGA-032 partly verified

`naut logix mode [--yes run|program] <project-dir>` reads or changes the controller's mode; a change takes `--yes`.

where the docs say so · The refusal without `--yes` is tested, and against a fake agent `naut logix mode` reads the mode through a project uploaded from the named controller (without changing it) and with `--yes program` changes it. The controller's mode actually changing is the SDK's, verified on hardware only.

  • pass TestLogixModeRefusesAChangeWithoutConfirmation in cmd/naut
  • pass TestLogixModeTakesTheProjectDirectory in cmd/naut
  • pass TestLogixModeReadsAndChangesTheMode in cmd/naut (3 of 3 passed)

LGA-033 verified

`naut logix write program.ld` writes an L5X without a project.

where the docs say so

LGA-034 partly verified

`naut test --target logix` runs the project's acceptance tests against the controller over EtherNet/IP: `given` writes go to the controller, `advance:` is wall time, `until`, `hold` and `always` are checked on every poll, and it waits on the `side.heartbeat` scan counter.

where the docs say so · Run against a stand-in controller (mechanics only); no test drives a real or emulated Logix controller through `naut test --target logix`. always: catches a transient violation per poll; `advance:` as wall time is asserted only as a positive elapsed time.

LGA-035 verified

The logixd agent URL comes from `target.logix.agent` and `NAUTILUS_LOGIXD_URL` overrides it; the token comes only from `NAUTILUS_LOGIXD_TOKEN`.

where the docs say so · With two fake agents: a deploy reaches the one named by target.logix.agent and every request carries NAUTILUS_LOGIXD_TOKEN as its bearer; with NAUTILUS_LOGIXD_URL set it reaches that one instead; a manifest with a `token:` under target.logix is refused.

  • pass TestClientDefaultsFromEnvironment in logix/logixd
  • pass TestLogixDeployFindsTheAgent in cmd/naut

LGA-036 verified

Anything outside the subset is refused, not approximated.

where the docs say so · Every rule in the page's table has a refusal case (trailing-underscore names and an AOI with an array parameter included), and FBD and SFC programs and a multi-task project are refused, each with nothing written.

  • pass TestRejections in logix/writer (31 of 31 passed)
  • pass TestStructuredTextRejections in logix/writer (9 of 9 passed)
  • pass TestRefusalsTheTableNames in logix/writer (4 of 4 passed)
  • pass TestLogixTargetRefusesSFCAndASecondTask in cmd/naut (4 of 4 passed)
  • pass TestCheckTargetLogixRefusesOtherLanguages in cmd/naut

LGA-037 not run in CI

An online edit keeps every value the controller holds; a download keeps the values of the tags it can match and still stops the controller.

where the docs say so · Verified on hardware only: the test downloads DemoLine with its values reset, sets HiLevelSP to 70 over EtherNet/IP, makes an online edit and expects 70 kept, downloads again and expects 70 kept, then downloads with ResetValues and expects the project's 85. It needs a controller that may be stopped (NAUTILUS_LOGIX_DOWNLOAD_COMM_PATH, NAUTILUS_LOGIX_HOST) and logixd, and skips in CI. Passed on an Echo controller (v38) on 2026-10-14 in 466 s.

  • skip TestHardwareDownloadKeepsValuesUnlessReset in logix/deploy

LGA-046 partly verified

The written project's `Local` module is Logix Designer's own for the ControlLogix 5580 (1756-L8xE), CompactLogix 5370 (1769-L30ER to 1769-L36ERM) and CompactLogix 5380 (5069-L306ER to 5069-L3100ERM) processors; for any other processor it is the 1756-L85E's, and `naut logix write` and `naut logix deploy` warn that it is, and a download from the editor reports the same warning in its progress.

where the docs say so · TestLocalModulesMatchExports writes a project for each of the 25 processors with an export in logix/writer/testdata/local (empty projects the SDK created on Logix Designer v38 and exported, committed verbatim) and compares the `<Modules>`, `<EthernetPorts>`, `<RedundancyInfo>` and `<Controller>` attributes with the export's; the table has a row for exactly those. TestLocalModule pins three families' modules and the warning for five processors with none; TestLogixRoundTripYardstickCLI sees the yardstick's 1769-L33ER get its own module and no warning. The deploy and serve call sites print the same writer.LocalModuleWarning and have no test of their own. That Logix Designer v38 imports (0 warnings, 0 errors) and builds a written project was checked by hand on ws-test for 5069-L320ER, 5069-L340ERM, 1769-L33ER, 1769-L36ERM and 1756-L83E (docs/design/logix-authoring.md, #373), not by a test here; no other Logix Designer release has opened one.

LGA-041 partly verified

With `naut logix serve --project`, an online edit from the editor is the same online edit `naut logix deploy --online` makes, and its progress and result reach the editor as they happen; a change it cannot carry is refused with the way to download it.

where the docs say so · Against a stand-in deploy function: the endpoint streams each progress line, then the report, and serves the new source; a NeedsDownloadError arrives as a 422 error event naming the download command. The deploy itself (logix/deploy.Session) has its own fake-agent tests; neither has run against a controller through the editor yet.

LGA-042 partly verified

A download from the editor is refused unless the request says yes; it sends the project with the Program-mode choice made, re-reads nautilus.yaml for it, releases the online session first and opens it again afterwards, so the next online edit is warm.

where the docs say so · Against the fake agent: no "yes" is a 400 and nothing runs; the held session is deleted before /download, an upload reopens it after, and the next edit makes no upload; a tag description added to nautilus.yaml after the server started is in the project sent. Not yet run against a controller.

  • pass TestDeployStreamDownload in logix/facade
  • pass TestLogixServePlaneDownloadRewarmsTheSession in cmd/naut
  • pass TestLogixServePlaneReleasesTheSessionBeforeADownload in cmd/naut
  • pass TestDeployStreamReportsADownloadBeforeItsWarmUp in logix/facade

LGA-048 partly verified

A download from the editor keeps the controller's tag values as the CLI does unless the request says resetValues (the extension sends it when nautilus.logix.resetValuesOnDownload is on), and its result carries how many values were kept and which tags were reset, and why.

where the docs say so · Against a stand-in download function: resetValues reaches it, and the done event carries valuesKept and resets (valuesKept -1 when values were reset). On an Echo controller (2026-10-14, #273) a download from the endpoint of an ST version of a ladder project kept 11 values and reported the two tags whose type changed (TIMER → FBD_TIMER, COUNTER → FBD_COUNTER). The modal wording and the setting are the extension's (logixDeployLogic.test.ts).

LGA-049 partly verified

A download from the editor reports as soon as it has verified: the online session for the next edit opens after the report, its progress following on the same stream, and an edit pushed meanwhile waits for that session and says so. Every result's time is the whole wait from the request, and a warm edit's progress reaches the editor as it happens.

where the docs say so · Against stand-in deploy functions and the fake agent: the done event is read while Warm is still blocked, GET /api/program shows no deploy in progress and an online edit goes through meanwhile, and the warm-up's line follows the report; elapsedMs counts a 150 ms wait the report's own 20 ms (deployMs) does not; the first line arrives while the deploy is still blocked; an edit queued behind the warm-up gets "waiting for the online session…" and then edits without an upload, each stream with its own lines; a warm edit logs "replacing …" and "verifying …" before its result. The extension shows the summary on the done event (logixDeployLogic.test.ts). Not yet re-measured on a controller (the #375 numbers predate it).

  • pass TestDeployStreamReportsADownloadBeforeItsWarmUp in logix/facade
  • pass TestDeployStreamWarmsOnlyAfterADownload in logix/facade
  • pass TestDeployStreamFlushesAtOnce in logix/facade
  • pass TestDeployStreamElapsedIsTheWholeWait in logix/facade
  • pass TestLogixServePlaneDownloadRewarmsTheSession in cmd/naut
  • pass TestLogixServePlaneEditWaitsForTheWarmUp in cmd/naut

LGA-050 partly verified

A plain download refused because the controller is in Run is answered with what to do — "Switch to Program Mode and Download" — instead of the SDK's raw RxE_INVALID_START_STATE, which stays available underneath; the editor offers the Program-mode download.

where the docs say so · Against the fake agent, which refuses a download in Run as the SDK does: the error is a StartStateError naming the mode and the way out, without the SDK code, and its Raw keeps the SDK's message and status lines; asked for Program mode and refused all the same, the SDK's answer goes as it is. The stream answers 409 with needsProgramMode and the raw text as detail. The button is the extension's (logixDeployLogic.test.ts). The SDK's wording is from the #375 run on an Echo controller; the new message has not been seen on one.

  • pass TestDownloadInRunSaysToSwitchToProgramMode in logix/deploy
  • pass TestDeployStreamStartStateRefusal in logix/facade

LGA-051 partly verified

A plain download to a controller in Run (or Remote Run) is refused as soon as the controller's mode is read, before the project is imported or built, with the same answer as the SDK's late refusal; asking for Program mode skips the check, and a mode read that fails does not block the download. An online edit's done event carries no download-only fields (valuesKept, resets).

where the docs say so · Against the fake agent, which reports a mode: in Run and Remote Run no build, save or download call is made after the refusal (with and without reset-values); Program mode on the request or on the controller goes ahead; a failing mode read reaches the build and the SDK's own refusal. Not yet seen on a controller: the time saved (the SDK's refusal came after ~76 s on Echo).

  • pass TestDownloadInRunRefusedBeforeBuild in logix/deploy
  • pass TestDownloadProgramModeSkipsTheModeCheck in logix/deploy
  • pass TestDownloadModeReadFailureFallsThrough in logix/deploy
  • pass TestLogixServePlaneRefusesADownloadInRunUpFront in cmd/naut
  • pass TestDeployStreamOnlineEdit in logix/facade

LGA-043 verified

Only one deploy runs at a time, and a deploy whose base is not what the controller runs is refused before anything is sent.

where the docs say so