# Step 7: newton-iteration-log-writer ## 읽어야 할 파일 - `AGENTS.md` - `docs/ARCHITECTURE.md` - `docs/nonlinear-static-newton-raphson/io.md` - `docs/nonlinear-static-newton-raphson/formulation.md` - `docs/nonlinear-static-newton-raphson/implementation-plan.md` - Step 0 files: `include/fesa/app/output_paths.h`, `src/fesa/app/output_paths.cpp`, `include/fesa/analysis/analysis.h` - Step 5 files: `include/fesa/analysis/nonlinear_static_control.h` ## 이전 Step 전제 Step 0 gives app-owned log path and lease lifetime. Step 5 defines convergence-channel data that log records consume. ## 소유 파일 - Create: `include/fesa/analysis/newton_iteration_log.h` - Create: `src/fesa/analysis/newton_iteration_log.cpp` - Create: `tests/unit/analysis/newton_iteration_log_test.cpp` - Modify: `src/fesa/CMakeLists.txt` - Modify: `tests/CMakeLists.txt` ## 인터페이스 ```cpp namespace fesa { enum class NewtonIterationStage { kEntered, kKinematicsEvaluated, kInternalAssembled, kExternalAssembled, kSolvedCorrection, kConvergenceEvaluated, kAccepted, kFailed }; struct NewtonIterationFailure { bool present; NewtonIterationStage stage; std::string diagnostic_code; std::string reason; }; struct NewtonIterationLogRecord { std::string step_name; std::uint32_t increment_index; double load_factor; std::uint32_t iteration; NewtonIterationStage last_completed_stage; NewtonIterationFailure failure; std::string transition; std::string outcome; std::string diagnostic_code; NewtonChannelDecision force_residual; NewtonChannelDecision moment_residual; NewtonChannelDecision translation_correction; NewtonChannelDecision rotation_correction; bool combined_passed; }; class NewtonIterationLogWriter { public: Status Open(const std::filesystem::path& log_path); Status Write(const NewtonIterationLogRecord& record); Status Flush(); Status Close(); }; } // namespace fesa ``` The serializer, not callers, owns the exact 39 labels and fixed order from `io.md`. For evaluated convergence channels it serializes only `relative` or `zero-scale` branch labels from `NewtonConvergenceBranch`. For unevaluated typed fields it writes `not-evaluated(reason)` using `NewtonIterationFailure::reason`. Callers cannot inject arbitrary fields. ## RED Add tests first. - `NSNR-LOG-001`: a complete Newton iteration writes exactly one LF-terminated line. - `NSNR-LOG-002`: line is UTF-8 without BOM, no header, no trailer. - `NSNR-LOG-003`: line has exactly 39 TAB-separated `label=value` fields in approved order. - `NSNR-LOG-004`: finite numeric values round-trip without locale dependence. - `NSNR-LOG-005`: each convergence channel serializes identity, `numerator_norm`, `denominator_scale`, branch, threshold, `threshold_unit`, and pass from `NewtonChannelDecision`. - `NSNR-LOG-006`: evaluated empty free-channel decisions serialize `numerator_norm=0`, `denominator_scale=0`, threshold `1.0e-3`, the channel-specific zero-scale `threshold_unit`, and branch `zero-scale`; no other branch label appears. - `NSNR-LOG-007`: combined pass, transition, outcome, failure stage, and diagnostic code are serialized from explicit record fields. - `NSNR-LOG-008`: early failure fields after the failed stage use `not-evaluated(reason)`. - `NSNR-LOG-009`: embedded tab, LF, CR, and backslash are escaped deterministically. - `NSNR-LOG-010`: write/flush/close failure returns `log-output-failure` and prevents success classification. - `NSNR-LOG-011`: non-Newton controlled failure path can leave a zero-byte log owned by app lifecycle. ### USER-DECISION-003 option-1 revision addendum Step 7의 completed status, summary, timestamps와 commits는 보존한다. Executor-selected Step 11에서 `tests/unit/analysis/newton_iteration_log_test.cpp`에 다음 RED assertion을 추가한다. - `NSNR-LOG-012`: synthetic `NewtonChannelDecision`이 아니라 `EvaluateNewtonConvergence`가 만든 residual decisions를 record에 넣는다. Exact-zero matching external channel은 denominator exact `0`, branch `zero-scale`, threshold `1.0e-3`, force 또는 force-length unit으로 직렬화하고, nonzero matching external channel은 retained denominator, branch `relative`, threshold `1.0e-2`, unit `1`로 직렬화한다. 같은 line이 기존 39 labels/order, TAB separation, LF termination을 그대로 유지해야 하므로 기존 selector에서는 RED가 된다. Run: ```powershell cmake --build --preset windows-msvc-ninja-debug ctest --preset windows-msvc-ninja-debug -R "NewtonIterationLog" --output-on-failure ``` Expected: new tests fail. Unexpected pass stops. ## GREEN Implement a small log writer with explicit open/write/flush/close state, serializer-owned 39-field order, deterministic escaping, finite numeric formatting, `relative`/`zero-scale` convergence branch mapping, typed `not-evaluated(reason)` substitution, and structured `Status` failures. USER-DECISION-003는 evaluator-produced decision semantics만 바꾸므로 log writer production 또는 `nsnr-log-v1` schema 변경은 예상하지 않는다. ## VERIFY ```powershell cmake --preset windows-msvc-ninja-debug cmake --build --preset windows-msvc-ninja-debug ctest --preset windows-msvc-ninja-debug -R "NewtonIterationLog" --output-on-failure ctest --preset windows-msvc-ninja-debug --show-only=json-v1 ctest --preset windows-msvc-ninja-debug --output-on-failure ``` Use the ignored `.harness/config.json` preset contract. The Harness process must inherit VsDevCmd x64 and `FESA_GTEST_SOURCE_DIR`, `MKL_DIR`, `TBB_DIR`, and `HDF5_DIR` from its parent launch environment. ## 상태 기록 Update only Step 7 status and one-line payload. Executor owns timestamps, commits, retry, and advancement. ## 금지사항 - Do not duplicate per-iteration log lines to stderr. 이유: approved log is file-only. - Do not accept arbitrary caller-provided field vectors. 이유: io.md requires an exact fixed schema and order. - Do not write HDF5 trial history. 이유: log is diagnostic and HDF5 stores accepted frames only. - Do not continue after log write/flush/close failure. 이유: log failure blocks result commit. - Do not add, remove, rename, reorder, or re-delimit any `nsnr-log-v1` field. 이유: option 1 changes only residual decision values, not the approved 39-field external log contract.