WriteProgramAsync resolved -path as a directory and combined it with
program.Name ("1" + "O0001" -> "1/O0001"), while ReadProgramAsync
resolved it as a program identifier under ProgramDirectory ("1" ->
"/PRG/1"). The write target therefore never matched the read target:
STOR went to a nonexistent relative dir and failed with "unable to
connect to remote server" (-204). Surfaced once ISS-018/019 stopped
masking it.
Per Mitsubishi NC Explorer manual IB-1500904, programs live under
PRG/USER and are addressed by their program name/number with no
extension. Resolve write the same way as read: ResolvePath is now
single-arg (path = identifier or full path, joined with
ProgramDirectory). program.Name is used for validation only.
Update write unit test to the symmetric contract and add a roundtrip
regression test (-path "1" -> /PRG/1).
Siemens/Fagor share the pattern but tie extension handling to name
(entangled with ISS-013); left open in ISS-020.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ISS-018: FanucProgram.Standardize() prepends a leading '\n' to the
program body, so the O#### header is not on lines[0]. The validator
first-line check read lines[0] and always failed "First line must be
O####", rejecting every valid Fanuc/Mitsubishi upload. Scan the first
non-blank line instead. Upload (FOCAS tape) format unchanged.
ISS-019: field-test .bat files passed -pathprogramma unquoted; on
install paths with spaces ("visual studio prove") cmd split the arg and
ArgParser captured only the first token -> truncated path. Quote the
arg in all 7 scripts.
Add regression test Validate_LeadingBlankLineBeforeONumber for both
Fanuc and Mitsubishi.
Surfaced by field testing against a local docker FTP server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>