diff --git a/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md b/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md index e0bba4c..2186074 100644 --- a/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md +++ b/docs/implementation-plans/linear-static-3d-euler-beam-implementation-report.md @@ -1289,3 +1289,42 @@ Step 27 can consume only its `physics_evidence` after Step 26 passes. - concerns: none; no critical implementation, environment, numerical, or upstream-contract blocker remains. + +### Step 24 Review Fix Round 1 — option-token output rejection + +- review_trigger: the approved CLI contract classifies repeated `--output` + and every unknown option as usage error exit 2. The original three-argument + parser treated the value position as an arbitrary nonempty path, so an + option token could advance into input analysis instead. +- RED-repeated-option: after adding + `{missingInput,"--output","--output"}` to the existing exact + `LinearStaticCli.ReturnsEveryExactExitCodeAndOrderedDiagnostic` test, + `cmake --build .harness/build --config Debug --target + fesa_integration_tests` exited 0 and the focused CTest exited 1. Actual exit + was 3 and stderr contained no `cli-usage`, rather than expected exit 2. +- GREEN-repeated-option: rejecting the literal repeated token made the same + focused test pass 1/1. +- RED-unknown-option: the reviewer then identified the general unknown-option + case. Adding `{missingInput,"--output","--bogus"}` before broadening the + production condition again produced build exit 0 and focused CTest exit 1; + actual exit was 3 with no `cli-usage`. +- production_fix: explicit output form now requires a nonempty third argument + that does not begin with `-`. Both a repeated known option and an unknown + option token are rejected before any input or output access, while ordinary + output paths retain the approved behavior. +- GREEN-focused: rebuilding `fesa_integration_tests` exited 0 under `/W4 /WX`; + the unchanged exact focused test passed 1/1 with both option cases pinned. +- independent_review: Critical 0, unresolved Important 0. The reviewer reran + exact Step 24 10/10, discovery 83, full 83/83, comparison 176 rows/16 + metrics passed, and reference diff/status clean. +- remaining_minor: the test-only comparator independently pins float64 result + file types but relies on the Step 23 writer self-check/full-suite schema test + for exact uint64 endianness and every compound member file dtype/UTF-8. This + is defense-in-depth negative coverage, not a reproducible approved-pipeline + false pass. +- remaining_minor: the inherited diagnostic sorter is not a literal + implementation of future multi-source declaration/internal-ID ordering, but + no V0 single-input CLI misordering is reproducible. +- remaining_minor: the Step 23 `libmmd.dll` guard remains unconditional across + alternate HDF5 targets; it is correct for the approved shared-HDF5 + environment and is an external portability limitation. diff --git a/phases/linear-static-3d-euler-beam/index.json b/phases/linear-static-3d-euler-beam/index.json index bc38c68..6ee3618 100644 --- a/phases/linear-static-3d-euler-beam/index.json +++ b/phases/linear-static-3d-euler-beam/index.json @@ -200,7 +200,7 @@ "status": "completed", "started_at": "2026-08-09T22:13:49+0900", "completed_at": "2026-08-09T22:54:33+0900", - "summary": "Exact factorize-before-load single-step lifecycle과 argv0-excluded fesa.exe 0/2/3/4/5/6 CLI를 구현하고 read-only legacy B33/HDF5 comparator로 176 rows, 16 metrics, deterministic JSON/physics evidence를 생성해 Step24 10/10 및 전체 83/83을 검증함" + "summary": "Exact factorize-before-load single-step lifecycle과 argv0-excluded fesa.exe 0/2/3/4/5/6 CLI를 구현하고 repeated/unknown option token을 usage로 거부하며 read-only legacy B33/HDF5 comparator로 176 rows, 16 metrics, deterministic JSON/physics evidence를 생성해 Step24 10/10 및 전체 83/83을 검증함" }, { "step": 25, diff --git a/src/fesa/app/fesa_application.cpp b/src/fesa/app/fesa_application.cpp index b7dfad5..17a970a 100644 --- a/src/fesa/app/fesa_application.cpp +++ b/src/fesa/app/fesa_application.cpp @@ -83,7 +83,8 @@ int FesaApplication::run(const std::vector& arguments) { !arguments[0U].empty() && !startsWithOption(arguments[0U]) && arguments[1U] == "--output" && - !arguments[2U].empty(); + !arguments[2U].empty() && + !startsWithOption(arguments[2U]); if (!defaultOutputForm && !explicitOutputForm) { writeDiagnostics({usageDiagnostic()}); return kUsageExitCode; diff --git a/tests/integration/app/fesa_application_test.cpp b/tests/integration/app/fesa_application_test.cpp index 57ce559..2642e87 100644 --- a/tests/integration/app/fesa_application_test.cpp +++ b/tests/integration/app/fesa_application_test.cpp @@ -332,7 +332,10 @@ TEST(LinearStaticCli, ReturnsEveryExactExitCodeAndOrderedDiagnostic) { const auto usage = runApplication({}); const auto missingInput = directory.path() / "missing.inp"; const auto input = runApplication({missingInput.string()}); - const auto repeatedInput = runApplication({missingInput.string()}); + const auto repeatedOutput = runApplication( + {missingInput.string(), "--output", "--output"}); + const auto unknownOutputOption = runApplication( + {missingInput.string(), "--output", "--bogus"}); const auto model = runApplication(explicitOutputArguments( modelInput, directory.path() / "model-failure.h5")); const auto solver = runApplication(explicitOutputArguments( @@ -353,8 +356,14 @@ TEST(LinearStaticCli, ReturnsEveryExactExitCodeAndOrderedDiagnostic) { expectDiagnosticFieldOrder(model.standardError); expectDiagnosticFieldOrder(solver.standardError); expectDiagnosticFieldOrder(output.standardError); - EXPECT_EQ(repeatedInput.exitCode, 3); - EXPECT_EQ(repeatedInput.standardError, input.standardError); + EXPECT_EQ(repeatedOutput.exitCode, 2); + expectDiagnosticFieldOrder(repeatedOutput.standardError); + EXPECT_NE(repeatedOutput.standardError.find("code=cli-usage"), + std::string::npos); + EXPECT_EQ(unknownOutputOption.exitCode, 2); + expectDiagnosticFieldOrder(unknownOutputOption.standardError); + EXPECT_NE(unknownOutputOption.standardError.find("code=cli-usage"), + std::string::npos); } TEST(LinearStaticCli, OutputRequestsDoNotFilterMandatoryResults) {