|
| 1 | +# Unworked Review Issues |
| 2 | + |
| 3 | +**Run:** 2026-06-05 17:39:00 |
| 4 | +**Task:** TASK-066 |
| 5 | +**Total:** 14 (0 critical, 1 major, 13 minor) |
| 6 | + |
| 7 | +## Major |
| 8 | + |
| 9 | +1. [ ] **code-simplifier** | `test/unit/hooks_log_access_alias_slot_test.cpp:178` | code-structure |
| 10 | + log_access_alias_is_immutable_after_construction duplicates roughly half of the pre-existing user_add_hook_grows_vector_alias_slot_untouched test (lines 91-110). Steps (a) and (b) of the new test—checking that construction wires the slot, that the vector starts at size 0, and that add_hook() grows the vector while leaving the slot set—are already pinned by the older test. The overlap means both tests must be updated if the invariant changes, and a reader cannot tell which test is authoritative for that assertion. |
| 11 | + *Recommendation:* Remove steps (a) and (b) from log_access_alias_is_immutable_after_construction and retitle it to reflect its unique focus: that remove() leaves the alias slot intact and that direct invocation still reaches the construction-time callable. Alternatively, delete user_add_hook_grows_vector_alias_slot_untouched and let the new test be the single owner of the full add/remove/invoke contract. |
| 12 | + |
| 13 | +## Minor |
| 14 | + |
| 15 | +2. [ ] **architecture-alignment-checker** | `src/hook_handle.cpp:146` | pattern-violation |
| 16 | + The comment on `erase_slot_for_handler_phase_` for `hook_phase::handler_exception` says 'any_hooks_ remains true while the alias is wired, which is correct -- the alias is the "still has a hook" signal.' This is correct behaviour but the comment conflates the user-vector gate with the alias-slot gate: the any_hooks_[handler_exception] flag is set to true by `install_internal_error_alias` when the alias slot is wired (see webserver_aliases.cpp:118). However, `erase_and_reset` only clears the flag when `hooks_handler_exception_` (the user vector) becomes empty -- it does NOT check whether `handler_exception_alias_` is also empty. If the alias is wired and the last user hook is removed, the flag stays true because the alias holds a live registration. The comment acknowledges this but does not cross-reference `install_internal_error_alias` line 118 where the gate is set for the alias. This is a documentation gap, not a logic error. |
| 17 | + *Recommendation:* Extend the comment to explicitly reference `install_internal_error_alias` (webserver_aliases.cpp:118) as the place that sets `any_hooks_[handler_exception]` for the alias slot, and clarify that `erase_and_reset` correctly avoids clearing the flag when the alias is still wired. |
| 18 | + |
| 19 | +3. [ ] **code-quality-reviewer** | `src/hook_handle.cpp:480` | code-readability |
| 20 | + fire_handler_exception (lines 454-519) contains inline error-logging calls that duplicate the log_hook_threw / log_hook_threw_unknown free-function pattern used by the template variants (lines 288-307). The two catch arms at lines 480-488 use .append() instead of the shared helpers. |
| 21 | + *Recommendation:* This pre-existing inconsistency is not introduced by TASK-066, but the task touched these lines. A follow-up to replace the inline log_dispatch_error strings in fire_handler_exception with calls to log_hook_threw / log_hook_threw_unknown would make the error-logging pattern uniform. |
| 22 | + |
| 23 | +4. [ ] **code-quality-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:178` | code-readability |
| 24 | + The block comment spanning lines 168-177 correctly explains the rationale for replacing the old test but is longer than the test body it introduces. Per clean-code comments rules, the code (test name + inline step comments) is already self-documenting, and the contextual history (what the old test did and why it was wrong) is better placed in the task/PR description than inline. |
| 25 | + *Recommendation:* Trim the block comment to one or two sentences that state the contract being tested; move the historical rationale to the commit message or TASK-066.md where it already lives. |
| 26 | + |
| 27 | +5. [ ] **code-quality-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:221` | test-coverage |
| 28 | + handler_exception_alias_is_immutable_after_construction does not include a direct invocation step (step (d)) comparable to the log_access test at line 211-218. The log_access test verifies the construction-time callable is still reachable after add/remove; the handler_exception mirror stops at vector-size assertions only. |
| 29 | + *Recommendation:* Add an invocation check analogous to lines 211-218 in the log_access test: build a handler_exception_ctx, call impl->handler_exception_alias_(ctx), and assert the return value is the expected response to confirm the construction-time callable remains wired after user-hook removal. |
| 30 | + |
| 31 | +6. [ ] **code-simplifier** | `test/unit/hooks_log_access_alias_slot_test.cpp:168` | comments |
| 32 | + The 10-line block comment before the two new tests (lines 168-177) reads as a commit-message changelog explaining why the old test was deleted and what the new tests pin. This is noise in the source file; the test names and inline step comments are already self-documenting. The comment also re-states the DR-012 / §4.10 contract that the @note in hook_handle.hpp now carries. |
| 33 | + *Recommendation:* Replace the block with a one-line comment that states the invariant being pinned, e.g. '// TASK-066: alias slots are construction-time-only; add_hook() and remove() must not reseat them.' |
| 34 | + |
| 35 | +7. [ ] **code-simplifier** | `test/unit/hooks_log_access_alias_slot_test.cpp:221` | code-structure |
| 36 | + handler_exception_alias_is_immutable_after_construction is asymmetric with its stated mirror (log_access_alias_is_immutable_after_construction): it omits the direct-invocation verification present in step (d) of the log_access variant. The comment on line 222 says 'Mirror of the log_access pin above', but the pin is incomplete — if the construction-time callable were silently cleared, this test would not catch it. |
| 37 | + *Recommendation:* Add a step (d) that invokes impl->handler_exception_alias_ directly with a synthetic handler_exception_ctx and asserts that the construction-time lambda was called, matching the pattern in the log_access test. |
| 38 | + |
| 39 | +8. [ ] **housekeeper** | `specs/tasks/M7-v2-cleanup/TASK-085.md:13` | action-item-not-marked-complete |
| 40 | + TASK-085 action item 3 ('Once TASK-066 ships the runtime setter (or pins the immutable-after-start contract), update this test to exercise the real path') is still unchecked (`[ ]`). TASK-066 both pinned the immutable-after-start contract and replaced the misleading test with `log_access_alias_is_immutable_after_construction` and `handler_exception_alias_is_immutable_after_construction`. TASK-066.md notes this ('TASK-085's action item 3... is satisfied here'), but TASK-085 itself has not been updated to reflect that AI-3 was completed by this task. |
| 41 | + *Recommendation:* Mark TASK-085 action item 3 as complete (`[x]`) and add a brief note that it was delivered by TASK-066 rather than waiting for TASK-085 to be worked. |
| 42 | + |
| 43 | +9. [ ] **performance-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:242` | memory-allocation |
| 44 | + In handler_exception_alias_is_immutable_after_construction, the lambda passed to add_hook wraps a std::function with an explicit construction: std::function<httpserver::hook_action(const httpserver::handler_exception_ctx&)>([...]). This causes a heap allocation for the std::function type-erased callable. In a test this is harmless, but the pattern is slightly more verbose and allocates where a direct lambda capture would also allocate — no real impact, just noting the pattern for consistency with the log_access test above it which passes a std::function<void(...)> the same way. |
| 45 | + *Recommendation:* No action required for a test. In production hot-path code, prefer passing callables through template parameters or using small-buffer-optimised wrappers if std::function allocation is a concern; this test path is not a hot path. |
| 46 | + |
| 47 | +10. [ ] **security-reviewer** | `src/hook_handle.cpp:218` | insecure-design |
| 48 | + The erase_and_reset lambda in hook_handle::remove() stores false into any_hooks_[handler_exception] when the user-vector becomes empty, without checking whether handler_exception_alias_ is still set. If a user registers a hook via add_hook(handler_exception, ...) and then removes it, any_hooks_[handler_exception] is cleared to false even though the alias slot is still live. This was pre-existing before TASK-066, but the task explicitly adopts the comment at line 142-146 that says 'any_hooks_ remains true while the alias is wired, which is correct' — yet the remove() path can falsify that invariant. In practice the dispatch path in webserver_dispatch.cpp at the server_chain check (line 372-373) directly ORs impl->handler_exception_alias_ into the gate, so the alias is never silently skipped even when any_hooks_ is wrong; the security impact is therefore nil in the current code. However, the invariant mismatch is a latent logic defect that could become a real bypass if a future caller relies solely on has_hooks_for(handler_exception) rather than the direct alias check. |
| 49 | + *Recommendation:* In the erase_and_reset lambda (hook_handle.cpp line 218), guard the any_hooks_ store with a check on whether the alias slot is still populated: replace 'if (vec.empty())' with 'if (vec.empty() && !impl->handler_exception_alias_)' for the handler_exception phase. The same pattern should apply for log_access_alias_ at the response_sent phase. Alternatively, add a helper any_hooks_for_phase_() that centralises the alias-aware check and is called by both remove() and has_hooks_for(). |
| 50 | + |
| 51 | +11. [ ] **spec-alignment-checker** | `specs/architecture/04-components/hooks.md:93` | specification-gap |
| 52 | + The §4.10 'v1 aliases' table describes not_found_handler as aliasing 'route_resolved (when route is nullopt; runs after any user route_resolved hooks that did not short-circuit)', but the implementation in webserver_aliases.cpp installs only an empty observation stub for route_resolved and the actual 404 synthesis still lives in webserver_impl::not_found_page. The architecture doc's parenthetical 'when route is nullopt' implies functional behaviour at the hook site, which does not match the stub. This is a documentation ambiguity rather than a code defect — TASK-066's own task document explicitly notes the structural deferral — but the arch doc could confuse readers. |
| 53 | + *Recommendation:* Clarify in the §4.10 not_found_handler row that the hook is an observation stub only; the 404 synthesis at this phase is deferred to a future task (per TASK-066 action item note). A parenthetical such as '(observation-only stub in v2.0; 404 synthesis deferred — see TASK-048)' avoids reader confusion. |
| 54 | + |
| 55 | +12. [ ] **test-quality-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:178` | missing-test |
| 56 | + The immutability tests cover only two of the five alias slots (log_access -> response_sent and internal_error_handler -> handler_exception). The three before_handler aliases (auth_handler, method_not_allowed_handler, not_found_handler/route_resolved) are installed via add_hook().detach() into the user vector rather than a dedicated slot, but TASK-066 explicitly documents all five as immutable-after-construction. A reader of this test file has no pinned contract for the other three. The omission is not critical because those three aliases write into the user vector (not a separate slot) so the immutability property is structurally different, but a comment or separate minimal test noting 'these three use vector entries, not slots' would complete the TASK-066 contract narrative. |
| 57 | + *Recommendation:* Add a brief comment (or a single baseline test) noting that auth_handler, method_not_allowed_handler, and not_found_handler aliases install into the user vector via detach() and are therefore not re-settable via any public API either. This avoids future confusion about why only two slots are pinned here. |
| 58 | + |
| 59 | +13. [ ] **test-quality-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:178` | multiple-concerns |
| 60 | + log_access_alias_is_immutable_after_construction combines four distinct assertions in one test body: (a) slot set at construction, (b) add_hook grows user vector and slot remains, (c) remove() shrinks vector and slot remains, (d) direct invocation reaches original callable. The test name names only the top-level invariant; sub-assertions (b), (c), (d) are individually distinct behaviors. The comments labelled (a)-(d) partially mitigate this, but a test failure in sub-assertion (c) or (d) requires reading the body to find the failing step. |
| 61 | + *Recommendation:* Consider splitting into two or three focused tests: one for the construction-time state, one for add_hook/remove not affecting the alias slot, and one for the callable remaining the original after add_hook/remove. The current structure is not wrong, but splitting would make failures self-diagnosing. |
| 62 | + |
| 63 | +14. [ ] **test-quality-reviewer** | `test/unit/hooks_log_access_alias_slot_test.cpp:221` | missing-test |
| 64 | + handler_exception_alias_is_immutable_after_construction does not include a direct-invocation sub-check (equivalent to step (d) in the log_access test). The log_access test verifies that calling impl->log_access_alias_(ctx) after add_hook/remove still reaches the original callable. The handler_exception test omits this step, leaving a gap: it proves the slot stays set (bool check) but does not prove the callable identity is preserved. |
| 65 | + *Recommendation:* Add a step (d) analogue to handler_exception_alias_is_immutable_after_construction: capture a counter in the internal_error_handler lambda, invoke impl->handler_exception_alias_(ctx), and assert the counter incremented. |
0 commit comments