# Step 0: cli-output-lease-and-derived-names ## 읽어야 할 파일 - `AGENTS.md` - `docs/SOLVER_AGENT_DESIGN.md` - `docs/HARNESS.md` - `docs/HARNESS_WORKFLOW.md` - `.codex/hooks.json` - `docs/nonlinear-static-newton-raphson/implementation-plan.md` - `docs/nonlinear-static-newton-raphson/io.md` - `docs/nonlinear-static-newton-raphson/requirements.md` - `src/fesa/app/fesa_application.cpp` - `include/fesa/app/fesa_application.h` - `include/fesa/analysis/analysis.h` - `tests/integration/app/fesa_application_test.cpp` - `src/fesa/CMakeLists.txt` - `tests/CMakeLists.txt` ## 이전 Step 전제 없음. 이 Step은 app/CLI output lifecycle의 첫 변경이다. ## 소유 파일 - Create: `include/fesa/app/output_paths.h` - Create: `src/fesa/app/output_paths.cpp` - Create: `tests/unit/app/output_paths_test.cpp` - Modify: `include/fesa/analysis/analysis.h` - Modify: `include/fesa/app/fesa_application.h` - Modify: `src/fesa/app/fesa_application.cpp` - Modify: `src/fesa/CMakeLists.txt` - Modify: `tests/CMakeLists.txt` - Test: `tests/integration/app/fesa_application_test.cpp` ## 인터페이스 ```cpp namespace fesa { struct DerivedOutputPaths { std::filesystem::path input_path; std::filesystem::path results_path; std::filesystem::path log_path; }; class OutputWriteLease { public: OutputWriteLease(const OutputWriteLease&) = delete; OutputWriteLease& operator=(const OutputWriteLease&) = delete; OutputWriteLease(OutputWriteLease&&) noexcept; OutputWriteLease& operator=(OutputWriteLease&&) noexcept; ~OutputWriteLease(); private: class Impl; friend Result AcquireOutputWriteLease( const DerivedOutputPaths& paths); explicit OutputWriteLease(std::unique_ptr impl) noexcept; std::unique_ptr impl_; }; Result DeriveSingleInputOutputPaths( const std::vector& arguments, const std::filesystem::path& invocation_cwd); Result AcquireOutputWriteLease( const DerivedOutputPaths& paths); struct AnalysisRequest { std::filesystem::path input_path; std::filesystem::path output_path; std::filesystem::path log_path; }; } // namespace fesa ``` `OutputWriteLease`는 acquisition-only private constructor와 private pimpl storage로 lock lifetime을 보존한다. Win32 handle, file handle, 또는 platform lock 표현은 `Impl` 안에만 둔다. `FesaApplication`은 lease를 log close와 HDF5 finalization이 끝날 때까지 보관한다. ## RED 테스트를 먼저 추가한다. - `NSNR-CLI-001`: argv의 모든 token을 왼쪽에서 오른쪽으로 검사하고, case-sensitive substring `--output`을 포함한 첫 token에서 exit code `2`와 diagnostic code `cli-usage`가 하나만 출력된다. - `NSNR-CLI-002`: `abc--outputdef`, `--output-path`, `--output=foo`, `--output` 모두 input path resolution 전에 실패한다. - `NSNR-CLI-003`: `--output` 실패는 parser, Analysis construction, lease acquisition, log create/truncate, HDF5 candidate creation을 호출하지 않는다. - `NSNR-CLI-004`: relative `.inp` path는 invocation CWD 기준으로 lexical normalization만 하고 `_results.h5`, `_log.txt`를 같은 directory에 만든다. - `NSNR-CLI-005`: final extension `.inp`는 case-insensitive이고 stem은 마지막 extension만 제거하며 basename case와 preceding dots를 보존한다. - `NSNR-CLI-006`: output-busy lease collision은 exit code `6`이며 log와 HDF5를 만들거나 바꾸지 않는다. - `NSNR-CLI-007`: previous result/log files가 있는 상태에서 `--output` 실패 후 bytes와 timestamps가 변하지 않는다. - `NSNR-CLI-008`: existing default `fesa.exe ` invocation은 더 이상 cwd `results.h5`를 기본값으로 쓰지 않는다. - `NSNR-CLI-009`: `AnalysisRequest`가 `input_path`, `output_path`, `log_path`를 함께 전달한다. 실행: ```powershell cmake --build .harness/build --config Debug ctest --test-dir .harness/build -C Debug -R "FesaApplication|OutputPath" --output-on-failure ``` 기대: 새 테스트가 컴파일 실패 또는 assertion 실패로 실패한다. 새 테스트가 예상 없이 통과하면 구현을 중단하고 Coordinator에게 보고한다. ## GREEN - `output_paths` module에 argv 검사, lexical path derivation, input extension validation, lease acquisition을 구현한다. - `FesaApplication`은 `DeriveSingleInputOutputPaths` 성공 뒤 `AcquireOutputWriteLease`를 호출한다. - 이 Step에서는 nonlinear procedure를 만들지 않는다. - `LinearStaticAnalysis` 호출은 기존 방식대로 유지하되 `AnalysisRequest::output_path`가 `_results.h5`를 가리키게 한다. ## VERIFY 실행 전 `Test-Path .harness\config.json`으로 Harness config를 재확인한다. 있으면 그 설정을 우선한다. 없으면 다음 순서로 실행한다. ```powershell cmake --fresh -S . -B .harness/build -G "Visual Studio 18 2026" -A x64 ` "-DFESA_GTEST_SOURCE_DIR=$gtestSource" ` "-DMKL_DIR=$mklDir" ` "-DTBB_DIR=$tbbDir" ` "-DHDF5_DIR=$hdf5Dir" cmake --build .harness/build --config Debug ctest --test-dir .harness/build -C Debug -R "FesaApplication|OutputPath" --output-on-failure ctest --test-dir .harness/build -C Debug --show-only=json-v1 ctest --test-dir .harness/build -C Debug --output-on-failure ``` ## 상태 기록 Executor가 선택한 current Step 0만 `completed`, `error`, `blocked` 중 하나로 갱신한다. 성공 시 summary 한 줄만 기록한다. timestamp, retry, commit, advancement는 Executor 소유다. ## 금지사항 - `--output` alias 또는 deprecation path를 남기지 마라. 이유: 승인된 public compatibility가 fail-closed removal이다. - path를 symlink, junction, short-name, case-normalized realpath로 resolve하지 마라. 이유: io.md가 lexical user-addressed identity를 요구한다. - log/HDF5 작업이 끝나기 전에 `OutputWriteLease`를 파기하지 마라. 이유: exclusive ownership 보장이 깨진다. - nonlinear analysis 분기를 `LinearStaticAnalysis` private lifecycle에 넣지 마라. 이유: Step 10의 별도 prepared flow가 소유한다.