v2.0: Modernization (M1-M6, 44 tasks)#374
Draft
etr wants to merge 361 commits into
Draft
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #374 +/- ##
==========================================
- Coverage 68.03% 67.08% -0.96%
==========================================
Files 34 30 -4
Lines 1730 2579 +849
Branches 697 1006 +309
==========================================
+ Hits 1177 1730 +553
- Misses 80 186 +106
- Partials 473 663 +190
... and 4 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
etr
added a commit
that referenced
this pull request
May 7, 2026
…rueFalse, exclude specs/ Codacy was reporting 2018 new issues on the v2.0 PR (#374). Resolve as follows: * Add .codacy.yaml excluding specs/** — the product spec, architecture notes, task records, and review notes are internal groundwork artifacts, not user-facing docs, and should not be subject to README markdownlint rules. Removes 2003 markdownlint findings. * src/webserver.cpp:499 — drop the redundant `blocking &&` from the wait loop condition. `blocking` is a function parameter never reassigned inside the loop body, so the conjunct was tautological (cppcheck knownConditionTrueFalse). * src/webserver.cpp:946 — replace the C-style `(struct detail::modded_request*)` cast on the MHD `cls` void* with `static_cast<detail::modded_request*>` (cppcheck cstyleCast). Mirrors the existing static_cast usage elsewhere in the file. * detail/webserver_impl.hpp, detail/http_request_impl.hpp, iovec_entry.hpp — add `// cppcheck-suppress-file unusedStructMember` with a one-line rationale comment. Every flagged member is in fact heavily used from the corresponding .cpp translation unit (registered_resources*, route_cache_*, bans, allowances, files_, path_pieces_public_, iovec_entry::base/len, etc.); cppcheck analyses each TU in isolation and cannot see those uses, so the warning is a known pimpl/POD false positive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
etr
added a commit
that referenced
this pull request
May 7, 2026
… clash Two unrelated CI regressions on PR #374, both falling out of TASK-020: 1. Lint job (gcc-14, ubuntu): cpplint flagged src/http_utils.cpp:30 with build/include_order, because the matching public header ("httpserver/http_utils.hpp") came AFTER a non-matching project header ("httpserver/constants.hpp"), and <microhttpd.h> (a C system header in cpplint's view) followed both. cpplint's expected order is: matching header, C system, C++ system, other. Reorder so the matching header comes first and the project headers ("constants.hpp" / "string_utilities.hpp") move to the bottom of the include block. 2. Windows MSYS2 build: src/httpserver/http_utils.hpp failed with error: expected identifier before numeric constant at the line `ERROR = 0,` inside the digest_auth_result enum. <wingdi.h> (pulled in via <windows.h> via <winsock2.h> via <microhttpd.h> on MinGW) unconditionally `#define`s ERROR to 0, and the preprocessor expands macros inside scoped-enum bodies just like anywhere else. Pre-TASK-020 the enum was inside `#ifdef HAVE_DAUTH`, so MSYS2 builds without digest auth never compiled it; PRD-FLG-REQ-001 then made the enum unconditional and exposed the latent collision. v2.0 is unreleased, so renaming is safe: ERROR -> GENERIC_ERROR (matches MHD_DAUTH_ERROR's "general error" docs). Static-assert pin in src/http_utils.cpp updated to match. Verified locally: - python3 -m cpplint on both touched files: exit 0. - `make check` on macOS: 32/32 PASS, all check-hygiene / check-headers gates PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address validation findings from /groundwork:validate (2 passes): - test/unit/routing_regression_test.cpp: fix always-green assertion in the overlapping-routes test (was *hp == first || *hp == second); add unregistered_path_yields_miss as the pure-miss baseline; add second_lookup_same_path_hits_cache_tier and unregister_invalidates_cache_entry to pin the LRU cache behaviour the gate's own rationale calls out; add FIXME(TASK-036-prereq) comment on the non_numeric assertions documenting the v1/v2 custom- regex divergence. - specs/tasks/_index.md: normalise TASK-028 status from Not Started to Done — the row was stale before this branch. - Recorded unworked review issues (31 minor findings: REGRESSION.md prose updates, naming/style nits, doc tweaks). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pins the v1 routing-test corpus against the new 3-tier table as the release-blocker gate (AR-003, §9 testing item 5). Adds test/unit/routing_regression_test.cpp covering all six v1 taxonomy rows (exact, parameterized single/multi-segment, prefix, regex, method-mismatched) plus pure-miss baseline, cache-tier hit, and cache invalidation on unregister. Documents the corpus in test/REGRESSION.md including the v2 lookup-canonicalisation fix in webserver.cpp (handle_request now strips trailing slash + query/fragment before lookup) and the one explicit divergence (custom regexes — pinned with FIXME(TASK-036-prereq)). Wired into make check via test/Makefile.am. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Public API rename (no aliases, per v2.0 breaking-changes policy):
- webserver::sweet_kill -> webserver::stop_and_wait
- webserver::ban_ip / unban_ip / allow_ip / disallow_ip
collapsed to webserver::block_ip(std::string_view) /
webserver::unblock_ip(std::string_view)
Per PRD-NAM-REQ-005 and OQ-004 the public surface keeps only deny-list
manipulation. The internal allowances set and the is_allowed branch in
policy_callback remain in place so default_policy(REJECT) keeps working
at the daemon level, but they are no longer reachable from the public
API. Coverage note: five ban_system tests that drove the allow-list
through the (now-removed) public API are deleted; a follow-up unit
test that pokes the PIMPL could restore that coverage at the cost of
breaking the PIMPL boundary, which is deliberately out of scope here.
webserver::stop() is unchanged ("stop without waiting" verb).
shoutCAST is preserved per OQ-005.
Action items:
- src/httpserver/webserver.hpp: new <string_view> include; old four
ban/allow declarations replaced with block_ip / unblock_ip; new
doxygen; sweet_kill renamed to stop_and_wait.
- src/webserver.cpp: stop_and_wait body unchanged; block_ip /
unblock_ip materialize std::string once at the boundary to call
ip_representation(const std::string&) (no string_view ctor added
to ip_representation).
- test/integ/ws_start_stop.cpp: sweet_kill test renamed.
- test/integ/ban_system.cpp: 4 retained tests renamed inline; 5
allow_ip/disallow_ip tests removed.
- examples/minimal_ip_ban.cpp: rewritten to demonstrate block_ip
under the default ACCEPT policy.
- examples/daemon_info.cpp: sweet_kill -> stop_and_wait.
- README.md: bullets + code samples updated.
Acceptance criteria verified:
- grep '\\bsweet_kill\\b' src/httpserver/*.hpp src/*.cpp -> empty
- grep '\\b(ban_ip|unban_ip|allow_ip|disallow_ip)\\b'
src/httpserver/*.hpp -> empty
- camelCase grep on src/httpserver/*.hpp returns pre-existing
matches only (comments, MHD/GnuTLS types, generateFilenameException,
shoutCAST). No method names introduced or touched by this task
use camelCase.
- make check (release + --enable-debug): 40/40 pass.
- cpplint: clean on all touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings recorded) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The spec-alignment-checker iter 1 review wrongly reported the rename as unimplemented (it had inspected the master branch instead of the merged TASK-029 worktree). Independent verification by every other reviewer and by the acceptance-criteria greps confirmed the rename was complete. The 10 false-positive entries (3 critical, 7 major) were removed; remaining 27 entries (6 major, 21 minor) describe real follow-ups. Worktree-absolute paths were also normalized to repo-relative. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codacy's "26 new issues (0 max.)" gate was failing on PR #374. Two classes of finding, addressed at root: - 21 markdownlint findings on test/REGRESSION.md (MD013 line-length, MD040 fenced-code language, MD043 heading structure). REGRESSION.md is an internal test-gate document (the v2.0 routing parity gate), conceptually peer to the already-excluded specs/ artifacts and not in the user-facing README/ChangeLog/CONTRIBUTING category. Extend .codacy.yaml exclude_paths with `test/**/*.md`. - 5 cppcheck findings that are all single-TU false positives: * iovec_entry.hpp: `cppcheck-suppress-file unusedStructMember` was not at the top of the file (preprocessorErrorDirective), so the file-level suppression was ignored and `base`/`len` were both flagged unused. Replaced with per-member inline suppressions. * route_cache.hpp: `cache_value::captured_params` is read in src/webserver.cpp at the cache-hit replay site; cppcheck does not follow the cross-TU read. Inline-suppress. * header_hygiene_test.cpp: cppcheck statically assumes none of the forbidden-header guard macros are defined and reports `leaks > 0` as always-false; the comparison is load-bearing at runtime under any actual leak. Inline-suppress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous commit added `test/**/*.md`, but in gitignore-style globs (which Codacy uses) `**` requires at least one subdirectory level, so it does not match `test/REGRESSION.md` at the top of `test/`. Codacy on bab42b8 still reported the 21 markdownlint findings. Add `test/*.md` alongside `test/**/*.md` so both top-level and any future subdirectory test markdown are excluded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the three error-page setters on `create_webserver` from the
`_resource` to the `_handler` suffix, retype them to take
`std::function<http_response(const http_request&)>` (return by value,
matching the on_* family from TASK-025/026), and mark
`webserver(const create_webserver&)` `explicit` so a stray
`webserver ws = some_create_webserver;` no longer compiles.
- `not_found_resource` -> `not_found_handler`
- `method_not_allowed_resource` -> `method_not_allowed_handler`
- `internal_error_resource` -> `internal_error_handler`
Surface changes:
- Drop the `render_ptr` typedef; introduce a public `error_handler`
typedef in `create_webserver.hpp` to keep the field/setter parameter
types in sync.
- Adapters in `webserver_impl::{not_found,method_not_allowed,
internal_error}_page` now wrap the by-value handler result via
`std::make_shared<http_response>(...)`; `force_our` semantics on
`internal_error_page` preserved.
- Constructor doc-comment replaced ("Keeping this non explicit on
purpose...") with a PRD-NAM-REQ-004 trace.
Compile-time sentinel:
- New `test/unit/create_webserver_explicit_test.cpp` pins both
guarantees via static_asserts:
* `!is_convertible_v<create_webserver, webserver>` (negative),
* `is_constructible_v<webserver, const create_webserver&>` (positive),
* positive SFINAE detectors for the three `_handler`-suffixed
setters with the by-value handler signature,
* negative SFINAE detectors confirming the `_resource`-suffixed
setters are gone.
- Wired into `test/Makefile.am` with empty LDADD (header-only compile
test, following the `webserver_pimpl` precedent).
Mechanical migration across the call-site graph:
- 250 implicit copy-init sites converted to direct-init
(`webserver ws = create_webserver(...);` -> `webserver ws{...};`)
across 45 files in `test/`, `examples/`, and `README.md`.
- All 11 setter-call sites renamed.
- 6 error-handler function/lambda bodies retargeted from
`shared_ptr<http_response>` to `http_response` by value
(custom_error.cpp, ws_start_stop.cpp, basic.cpp, create_webserver_test.cpp).
- README error-page documentation updated to reflect the new
`std::function<http_response(const http_request&)>` signature.
Acceptance gates (all empty as required):
- `grep -rE '(not_found|method_not_allowed|internal_error)_resource'
src/httpserver/*.hpp`
- `grep -rEn '\brender_ptr\b' src/ test/ examples/`
- `grep -rEn '\bwebserver\s+\w+\s*=\s*(httpserver::|ht::)?create_webserver\b'
test/ examples/ README.md`
- `grep -n 'NOLINT(runtime/explicit)' src/httpserver/webserver.hpp`
All 41 tests pass (40 prior + 1 new sentinel). cpplint clean on every
touched file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update specs/tasks/_index.md and specs/tasks/M5-routing-lifecycle/TASK-030.md to show "In Progress" for TASK-030, reflecting the worktree's active state before the branch is merged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ings recorded) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lands the 6-point dispatch error-propagation contract from §5.2 /
PRD-FLG-REQ-002 / DR-009.
API change (v2.0 breaking, intentional):
- `internal_error_handler_t = std::function<http_response(const
http_request&, std::string_view)>` is a new typedef carrying the
originating exception's message. `error_handler` continues to serve
`not_found_handler` and `method_not_allowed_handler` (which receive
no message). The widening is the smallest deviation consistent with
action item 2 ("invoke `internal_error_handler` with `e.what()`").
Dispatch path (`webserver_impl::finalize_answer`):
- Replaces the single `catch (...)` with the contract's two-branch
form: `catch (const std::exception& e)` forwards `e.what()` to the
user handler; `catch (...)` forwards the literal `"unknown
exception"`. Each arm logs via `parent->log_error` first.
- New `run_internal_error_handler_safely(mr, msg)` contains a possible
double-throw from the user handler. On re-throw it logs generically
and returns a hardcoded empty-body 500 via
`internal_error_page(mr, "", force_our=true)`.
- `internal_error_page` now takes `(mr, msg, force_our=false)`:
* `force_our=true` -> `http_response::empty().with_status(500)`.
* handler set -> invoke it with `(*mr->dhr, msg)`.
* handler unset -> default body == `msg` so the unset-handler
path is informative for debugging.
- The three non-handler-throw call sites that synthesise a 500
(handler-returned-null, materialize-returned-null, materialize-
threw) now route through `run_internal_error_handler_safely` so a
misbehaving user handler can't escape into libmicrohttpd.
`feature_unavailable` (a `std::runtime_error` subclass) lands as a
generic 500 — no special status mapping per DR-009 point 5.
Tests:
- 8 new `dr009_*` integration tests in `test/integ/basic.cpp` pin all
four acceptance criteria (message-surfaces-in-body,
message-passed-to-handler, logged-via-error_logger, empty-body-on-
double-throw, generic-log-on-double-throw, unknown-exception-
sentinel, feature_unavailable-as-500). Capturing handlers/loggers
use std::mutex since MHD invokes them concurrently.
- `exception_forces_500`, `untyped_error_forces_500`,
`file_serving_resource_missing`, `file_serving_resource_dir`
updated: body assertions changed from `== "Internal Error"` to
substring-find of the new diagnostic message.
- `internal_error_handler_also_throws`, `builder_internal_error_
handler`, `create_webserver_explicit_test` updated to the widened
handler signature. The explicit test gains a static_assert pinning
that the legacy single-arg signature is now rejected.
Documentation:
- Class-level Doxygen on `webserver` lists the 6 points verbatim with
cross-references to PRD-FLG-REQ-002 and DR-009.
- Per-setter Doxygen on `create_webserver::internal_error_handler`
documents the new signature, the unset-handler default, the
double-throw contract, and `feature_unavailable`'s 500 status.
- Inline comments on `internal_error_page`, `log_dispatch_error`, and
`run_internal_error_handler_safely` document the contract from the
implementation side. README pass is M6 (TASK-041).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings recorded) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds test/integ/threadsafety_stress.cpp with two sub-tests: 1. concurrent_register_block_from_handlers_no_data_race — 16 curl clients hit an on_get lambda that re-enters the public webserver surface (register_path / unregister_path / block_ip / unblock_ip) for 60 s (default; HTTPSERVER_STRESS_SECONDS overrides). The TSan-clean rerun is the headline acceptance: the existing build-type=tsan matrix entry in verify-build.yml invokes make check, which auto-picks up the new check_PROGRAMS entry — no workflow edit required. Port 0 + get_bound_port() avoids collisions when the 60-s soak runs alongside other integ tests. 2. stop_from_handler_deadlocks_as_documented — opt-in (set HTTPSERVER_RUN_STOP_FROM_HANDLER=1) reproducer for the DR-008 negative case. Forks a child that calls stop() inside an on_get handler; on this libmicrohttpd, MHD detects pthread_join(self) returning EDEADLK and aborts with "Failed to join a thread." The fork contains the abort so the parent test binary stays healthy. Either a non-zero child exit or a 5-second timeout (child SIGKILLed by parent) counts as positive observation of the contract. Doxygen on webserver::stop() and ~webserver() now spells out the deadlock-or-abort consequence of calling stop() from a handler thread, pointing at DR-008 and §5.1. Test wall-clock: 60 s default, ~5 s minimum locally. Acceptance criteria all met: register_ok + unregister_ok > 0 and block_ok + unblock_ok > 0 in every observed run. Full make check -j1 passes 42/42. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings recorded) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse 17 paired `foo()/no_foo()` setters into single `foo(bool enable = true)` setters on create_webserver, rename the remaining one-way negative flags (`no_listen_socket`, `no_thread_safety`, `no_alpn`) to their positive counterparts (`listen_socket`, `thread_safety`, `alpn`), and add setter-time validation that throws `std::invalid_argument` with a parameter-named message: - port: int overload validates [0, 65535]; uint16_t overload preserved - max_threads, max_connections, memory_limit, connection_timeout, per_IP_connection_limit, max_thread_stack_size, nonce_nc_size, listen_backlog, address_reuse, tcp_fastopen_queue_size: reject < 0 - client_discipline_level: reject < -1 (-1 is the unset sentinel) - file_upload_dir: reject empty string - bind_address(string): error message now prefixed with "bind_address:" Header collapsed from 593 lines (feature/v2.0) / 554 lines (master baseline) to 253 lines — 54% under the v1 baseline (PRD §3.3 target was 30%). Acceptance grep `grep -E '^\s*create_webserver& no_' src/httpserver/create_webserver.hpp` now returns empty. Internal `webserver`-side field names (`no_listen_socket`, `no_thread_safety`, `no_alpn`) are unchanged so `webserver.cpp` does not churn — only the public API is renamed. Tests: new unit tests in test/unit/create_webserver_test.cpp cover port-out-of-range, every numeric validator, file_upload_dir empty, bind_address parameter-name message, and the bool-arg shape for every toggled flag (including the renamed listen_socket/thread_safety/alpn and widened tcp_nodelay/turbo/suppress_date_header/sigpipe_handled_by_app). 77 tests, 81 checks pass. Full `make check` green (42/42). Internal callers rewritten: test/integ/ws_start_stop.cpp, test/integ/file_upload.cpp, test/unit/routing_regression_test.cpp, examples/file_upload.cpp, examples/external_event_loop.cpp (comment). README.md updated to document the bool-arg public API. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ings recorded) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove #ifdef HAVE_BAUTH/HAVE_DAUTH/HAVE_GNUTLS/HAVE_WEBSOCKET guards from public headers. Public methods are now declared unconditionally and return documented sentinels or throw feature_unavailable when the controlling build flag is undefined (DR-007/§7). Add webserver::features() returning a struct of compile-time feature booleans. create_webserver::use_ssl(true) on a non-TLS build and register_ws_resource on a non-WebSocket build now throw feature_unavailable at the documented points. Acceptance: grep '#if(def)? HAVE_(BAUTH|DAUTH|GNUTLS|WEBSOCKET)' src/httpserver/*.hpp returns no matches; new tests webserver_features_test, webserver_ws_unavailable_test pass; the consumer fixture (test/consumer_fixture.cpp) compiles unchanged across configurations. Refs: PRD-FLG-REQ-001..004, DR-007. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t digest auth) and housekeeping (status Done in _index.md) - Add HAVE_DAUTH guard so digest_auth(true) on a build without digest support throws feature_unavailable. - Guard http_request::check_digest_auth against null underlying connection (test-request path). - Add test/unit/webserver_dauth_unavailable_test.cpp covering the new throw site. - Update specs/tasks/_index.md: TASK-034 status -> Done. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Capture the iter-2 validation review findings that were intentionally left unworked — the iter-2 pass approved across all 8 active agents and none of the remaining findings were blocking. Persisted via the standard persist-unworked-findings.js helper.
…es() (PRD-FLG-REQ-001..005)
Mirror TASK-023's ownership pattern on the websocket-registration
surface (PRD-HDL-REQ-003, PRD-HDL-REQ-005, DR-010).
Public API changes (webserver.hpp):
- Removed: bool register_ws_resource(string, websocket_handler*).
- Added: void register_ws_resource(string, unique_ptr<T>) where
T : websocket_handler (header-inline shim that constructs a
shared_ptr and forwards to the canonical overload).
- Added: void register_ws_resource(string, shared_ptr<websocket_handler>).
- Added: void unregister_ws_resource(string).
- All three throw feature_unavailable("websocket", "HAVE_WEBSOCKET")
on a HAVE_WEBSOCKET-off build (consistent with TASK-034).
Internal changes (webserver_impl.hpp, webserver.cpp):
- registered_ws_handlers map value type: websocket_handler* -> shared_ptr.
- ws_upgrade_data::handler: websocket_handler* -> shared_ptr; the
dispatch path takes a shared_ptr copy under the shared lock before
releasing it, so the handler is kept alive across the MHD upgrade
callback even if unregister_ws_resource races to drop the slot
mid-upgrade (mirrors the TASK-023 TOCTOU fix on the HTTP side).
- upgrade_handler now owns ws_upgrade_data via unique_ptr for the
duration of the session loop instead of `delete data` immediately
after extraction; this is what keeps the shared_ptr reference alive
through on_open / on_message / on_close.
- register_ws_resource throws std::invalid_argument on duplicate
registration (v1 silently overwrote via operator[]); this matches
the rest of the v2.0 registration surface.
Tests:
- New: test/unit/webserver_register_ws_smartptr_test.cpp. Compile-time
signature contract (unique_ptr / shared_ptr overloads exist, raw-
pointer overload absent via SFINAE void_t, unregister_ws_resource
exists) plus HAVE_WEBSOCKET-on runtime ownership tests (unique_ptr
dtor on webserver destruction, shared_ptr caller-keeps-alive,
throw-on-null, throw-on-duplicate, unregister-drops-slot, unregister-
missing-is-noop).
- Updated: test/unit/webserver_ws_unavailable_test.cpp. Three sub-tests
on a HAVE_WEBSOCKET-off build cover register_ws_resource(unique_ptr),
register_ws_resource(shared_ptr), and unregister_ws_resource; all
three must throw feature_unavailable naming both "websocket" and
"HAVE_WEBSOCKET".
- Updated: test/consumer_fixture.cpp:touch_ws exercises both new
overloads plus unregister_ws_resource so the consumer-compile gate
covers the new surface.
Callsite migration:
- examples/websocket_echo.cpp: now uses make_unique<echo_handler>().
Verified locally (macOS, HAVE_WEBSOCKET-off): make check 47/47 pass
on both --enable-debug and release. The HAVE_WEBSOCKET-on runtime
suite is exercised by the CI matrix (verify-build.yml).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… review Major items (all 5 addressed): - [1] register_ws_resource smart-ptr overloads: already done by TASK-035, verified - [2,7] HAVE_DAUTH constructor guard: already done (webserver.cpp:258-266), verified - [3,10,12] features() body: refactored to four constexpr bool k_* locals + flat return - [4] websocket_handler stub repetition: extracted [[noreturn]] throw_ws_unavailable() helper - [5] webserver_features_test branching: moved to k_expected_* constexpr consts outside test body Minor items fixed: - [6] httpserver.hpp: update C++ guard 201703L → 202002L (DR-001 / C++20 floor) - [8] 07-feature-availability.md: document HAVE_DAUTH construction-time throw - [9] create_webserver.cpp: trim digest_auth_default() comment to match basic_auth_default() - [14] webserver_dauth_unavailable_test: add explicit_digest_auth_false_does_not_throw test - [15,29] webserver_dauth_unavailable_test: add listen_socket(false) + remove ws.stop() - [17] http_request_auth.cpp: add sentinel comment to get_pass() #else branch - [18,19] http_request_auth.cpp: consolidate get_digested_user() null+empty guards - [31] create_webserver_test: add basic_auth_true_succeeds_when_bauth_available test Minor items deferred: 11, 13, 16, 20, 21, 22, 23, 24, 25, 28, 30, 32 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # src/websocket_handler.cpp
…eview-cleanup) Major findings (5 total): - #1 (adr-violation): implementation is correct per DR-012/DR-009 §5.2; deferred. - #2/#3/#28 (code-structure, triplicate): extracted append_impl<P,Sig> template helper in resource_hook_table.cpp anonymous namespace; each of the five append_* methods now delegates in one line (mirrors fire_short_circuit_impl / fire_void_impl pattern). - #4/#5 (test-structure, advisory): deferred — project prefers per-case explicit test bodies for independent failure reporting. Key minor fixes applied (cosmetic, no behavior change): - TOCTOU anti-pattern (#6/#35/#36/#45/#47/#48): removed expired()+lock() double-check from per_route_table() helper; fire_request_completed_gated now uses the helper consistently (was inline-expanded). - Shadow variable (#15/#38): renamed local var per_route_table → rtable in fire_before_handler_gated, consistent with other gated-fire helpers. - Lifetime comment (#12): added "res keeps the resource alive while rtable is in use" note in handle_dispatch_exception. - Memory-order comment (#50): documented acquire-chain at rtable fetch site in fire_before_handler_gated. - Sentinel assertions (#41/#61/#62): removed LT_CHECK_EQ(true, true) from hooks_per_route_resource_destroyed_first.cpp and hook_api_shape_test.cpp; replaced with descriptive comments. - resource_hook_table.hpp comments (#8): clarified named-vector vs std::array tradeoff and any_hooks_ unused slots. - http_resource.hpp (#24/#27): added copy-shares-hook-table note; added comment before HTTPSERVER_COMPILATION guard. - http-resource.md / DR-012.md (#9/#42/#43): documented per-route hook bus and PIMPL storage choice. All 62 items marked [x] in specs/unworked_review_issues/2026-05-26_230100_task-051.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # specs/architecture/04-components/http-resource.md # src/detail/webserver_finalize.cpp # test/unit/hook_api_shape_test.cpp
Major fixes: - Extract as_shared() to test/integ/test_utils.hpp (DRY: removes 10 verbatim copies from integ TUs; fixes majors #2, #3 and minors #9, #16, #27). Add safety contract documentation in the header. Update test/Makefile.am noinst_HEADERS. - Add performance note in webserver_impl.hpp documenting the v1 route_cache_mutex_ serialization bottleneck and future migration path to route_cache_v2 (major #4). - Document §4.7 compliance status: route_entry variant exists in the v2 3-tier table (route_entry.hpp); v1 maps are transitional legacy flagged for Cycle K removal (major #1). Minor fixes: - Reword duplicate-registration comment to accurately describe both unique_ptr and shared_ptr ownership paths (#7, #8). - Add lock-order explanation before manual registered_resources_lock .unlock() call (#15, #18). - Rename is_exact to is_plain_path with explanatory comment (#14). - Rename fe to exact_it in resolve_resource_for_request (#17). - Add comments documenting necessary shared_ptr copies on hot paths to prevent reviewers from "optimising" them away (#19, #21). - Add curl_global_cleanup() after curl_easy_cleanup in unique_ptr_overload_compiles_and_serves (#11). - Document PORT macro sequential assumption and ephemeral-port migration path (#10, #13). - Add compile-time vs runtime note on unique_ptr_overload test (#28). - Rename threw to caught_invalid_argument in null/dup throw tests with post-catch assertion (#29). - Add comment in set_up documenting dtor_count reset dependency (#30). - Various already-addressed items noted in spec (ws raw pointer removed #5/#26, unregister implicit-conversion bug fixed #22). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Major (finding 1): Already addressed in merged feature/v2.0 code via unique_ptr data_guard in complete_websocket_upgrade. Security minors (findings 14-15): - data_guard.release() now conditional on MHD_queue_response == MHD_YES; if MHD_queue_response fails the RAII destructor frees the allocation. - MHD_websocket_create_accept_header failure now aborts the upgrade (destroys response, returns nullopt) instead of sending 101 without the required Sec-WebSocket-Accept header (RFC 6455 §4.2.2). Code quality (findings 2, 4, 7-9): - registered_ws_handlers in webserver_impl.hpp gets "Lock: protected by registered_resources_mutex" comment (findings 2 & 4). - websocket_handler forward decl in webserver_impl.hpp made unconditional to match public webserver.hpp (finding 9). - Local var renamed key -> url_key in webserver_routes.cpp (finding 8). - Ownership comment already present via CWE-401 block (finding 7 satisfied). API documentation (finding 16): - Added @pre precondition to both register_ws_resource overloads in webserver_websocket.hpp documenting the before-start() requirement. Test improvements (findings 5, 6, 18-20): - unregister_ws_resource_drops_handler: uses counted_ws_handler + asserts dtor_count == 1 after unregister to confirm full ownership release. - webserver_ws_unavailable_test: inline comment clarifying null is for compile convenience only, not a null-specific contract. - Comment above null_unique_ptr_throws explains both null tests reach the same shared_ptr null-guard and documents the try/catch workaround. - New derived_unique_ptr_accepted_by_shim test pins SFINAE resolution for derived types (mirroring TASK-023), satisfying finding 20. Deferred (no action recommended): findings 3, 11, 13, 17. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 44 items (3 major, 41 minor) in the 2026-05-26_000000 first-pass review file are now cross-referenced against the 2nd-pass cleanup (fix/task-051-2nd-review-cleanup, commit d5dd7fd). Each item is marked with its disposition: - ALREADY FIXED: 19 items already addressed by 2nd-pass (append_impl template, expired()+lock() TOCTOU removals, LT_CHECK_EQ sentinels, http-resource.md arch doc, per_route_table comment, variable renaming, lifetime comments, memory-order comment) - ALREADY ADDRESSED: 3 items where the recommendation itself said no action was needed (remove_slot repetition, test sufficiency note) - DEFERRED: 22 items carried as advisory or follow-up (thread_local perf, C++20 migration, test naming, boundary tests, etc.) No source or test files changed; this is a housekeeping annotation pass only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ready fixed)
Major (performance):
- commit_handlers_to_shim: pass handler by value, copy into N-1 slots,
move into last slot — eliminates one extra heap allocation per
multi-method registration (performance-reviewer-iter1-1)
- Updated declaration in webserver_impl_dispatch.hpp to match
Cosmetic / documentation fixes:
- on_methods_: add null-byte path guard (CWE-20, security-iter1-23)
- on_methods_: add security comment documenting exact-only semantics in
single_resource mode (CWE-284, security-iter1-22)
- route(method_set,...): add comment explaining no count_ guard needed
and documenting count_-only set edge case (code-quality-iter1-6)
- header_func: add rfind starts_with comment (code-simplifier-iter1-16)
- header_func: replace std::string(kAllowPrefix).size() with strlen()
(code-simplifier-iter1-17)
Test additions:
- route_delete_serves_delete_request: exercise DELETE beyond GET/POST
(code-quality-iter1-7, PORT+15)
- route_duplicate_method_path_throws_for_post: method-agnostic conflict
check (code-quality-iter1-9, PORT+16)
- route_root_path_serves_get_request: root path sanity (test-iter1-30,
PORT+17)
- route_method_set_count_sentinel_only_behavior: pin current behaviour
for method_set{}.set(count_) (test-iter1-29, PORT+18)
- Rename route_only_allows_registered_method ->
route_get_returns_405_with_allow_header_for_post_request
(test-quality-iter1-27)
Already-fixed items (verified, no action taken):
- explicit constructor (arch-iter1-2)
- method_set::empty() predicate (code-quality-iter1-3)
- for_each_requested_method helper (code-quality-iter1-4/5/14/15)
- is_new_entry naming (code-simplifier-iter1-12/13)
- assert+unconditional guard in lambda_resource::invoke_
(security-iter1-21)
- TASK-036 comment in lambda_resource (performance-iter1-18)
Deferred: TIME_WAIT convention (8), ws.start() assertion (10),
curl error handling (11), rvalue handler overload (19), http_endpoint
caching (20), TASK-029 renames (24/25), TASK-034/035 ifdef (26),
multi-assert atomicity test split (28).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All items checked: 1 major fixed, 12 minors already fixed by prior tasks, 11 minors fixed in this pass, 6 minors deferred (pre-existing TASK-029/034/035 items, project conventions, API-width concerns). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tic_assert Remove TASK-021 ticket-prefix annotations from permanent source and test comments (modded_request.hpp, webserver_impl_dispatch.hpp, webserver_request.cpp, webserver_routes.cpp, method_utils.hpp, http_method.hpp, http_resource.hpp, webserver_route_test.cpp, http_resource_test.cpp, basic.cpp). Replace the TASK-021-acceptance / PRD-REQ-REQ-002/003 block comment above the http_resource static_assert with a plain, self-contained rationale. Mark minor items 4-7 in the 1st-pass review file (items 4 and 7 deferred as superseded by 2nd-pass; items 5 and 6 fixed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Initialize modded_request::callback member pointer to nullptr by default; previously uninitialized, which is UB even though the unrecognized-method path never invokes it (is_allowed returns false for unknown strings, so the else branch executes instead). - Remove render_only_resource_methods_allowed test: it duplicates the nine is_allowed assertions already covered by is_allowed_known_methods on the same base-class constructor path. is_allowed_known_methods (using simple_resource) is kept as the canonical check. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Second-pass review of TASK-021 (webserver/method_set bitmask era). Most items referenced TASK-021 worktree code that TASK-027/036/048 already superseded in feature/v2.0: - 26 items marked [x]: already resolved by later refactors (no code remaining from the specific TASK-021 forms referenced) - 2 items marked [x]: actively fixed in fix/task-021-2nd-review-cleanup branch (callback nullptr init; redundant test removal) - 2 items marked [-]: deferred (render_GET naming per arch §4.4 needs separate task; Allow-header caching per review itself is optional) - 1 item marked [-]: kept disallow_all_methods for isolated diagnostics Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # src/httpserver/details/modded_request.hpp # test/unit/http_resource_test.cpp
…files Sweeper agents verified each unchecked finding against the current code on feature/v2.0 and added a *Status:* line (Resolved / False positive / deferred) where one was missing. Coverage: - 22 review files swept (task-007/008/010/011/012/013/019/020/022/023/028/ 029/030/036/040/042/047/049 plus three already fully dispositioned) - ~330 items dispositioned this pass - 0 remaining unchecked items without a *Status:* / *Fixed:* / *Deferred:* marker across the whole specs/unworked_review_issues/ directory Notable verified-fixed clusters: TASK-011/012/013 closed most of TASK-010's http_response findings; TASK-046/048/049 closed most of TASK-047's hook-bus findings; TASK-027 cache + ban-system work closed most of TASK-019/020/030. Notable deferrals worth a follow-up pass (queued for Pass 2): - task-019 #22: A09 password plaintext in http_request operator<< - task-010 #23/#24: input-validation gaps in http_response file/pipe factories - task-036 #37: v2 3-tier route table (TASK-027) built but never wired into dispatch hot path - task-036 #38: auth_handler_ptr migration to optional<http_response> - task-040 #58–#61: hardcoded creds + reflected XSS + path traversal in example code (users will copy these) - task-047 #3/#4/#5: hook_handle::remove() switch refactor + fire_hooks unification + curl helper extraction No code changes in this commit — only review-file dispositions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the highest-signal deferred findings surfaced by Pass 1: examples/ — security fixes copied by users (CWE-798, CWE-79, CWE-22): - basic_authentication.cpp: read BASIC_USER/BASIC_PASS from env; bail if unset; capture into the lambda. Removes hardcoded "myuser"/"mypass". - digest_authentication.cpp: read DIGEST_PASS from env; bail if unset. - file_upload.cpp: add html_escape() helper and escape every user-controlled field (key, filename, fs path, content-type, transfer encoding) before writing into the HTML table. - file_upload_with_callback.cpp: html_escape() the filename in the HTML body and add is_safe_filename() guard (rejects empty/./../slash/ backslash/NUL) before joining with permanent_dir. test/REGRESSION.md §4 — prose drift: - The pinned overlap test now asserts `*hp == first` (deterministic first-wins) but the prose still said "could be either one". Updated to match the actual assertion and remove the v1-era hedge. Closes task-040 #58 #59 #60 #61 and task-028 #9 #25 in the unworked review issues tracker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 of 45 deferred TASK-009 findings were scoped to TASK-011/012/013, which have since merged. Verified each against the current code and closed: - TASK-013 follow-ups (final, v1-compat header removal, virtual *_response method removal, MHD forward-decl cleanup, friends-private, AC/static_assert in TASK-013.md): http_response is final at http_response.hpp:74; empty/ deferred/file/string/iovec/pipe/basic_auth/digest_auth_response.hpp gone. - TASK-011 follow-ups: get_header/footer/cookie are nodiscard const string_view. - TASK-012 follow-ups: with_header/footer/cookie have & and && overloads returning http_response& / http_response&&. - namespace details → detail consistent across src/, test/, docs/. - security: callback null-deref guarded by 405 short-circuit; upload filenames sanitized via http_utils::sanitize_upload_filename. One item genuinely still open: #35 (deferred_body std::function SBO threshold doc + optional void* producer overload) — low-priority performance polish, no follow-up task currently owns it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final cleanup pass over the unworked review issues tracker: - Flipped 7 checkboxes whose *Status:* already indicated Resolved / Accepted / No-action (task-028 #9/#25, task-031 #25/#27/#28/#29/#35). - Converted 74 clearly-cosmetic deferrals (naming preferences, idiom choices, comment trim suggestions, "consider renaming" notes) to explicit *Status:* wontfix. Kept the checkbox as [ ] so they remain visible in the open list but are no longer in the actionable backlog. Final state of specs/unworked_review_issues/: - 1974 total findings across 37 review files - 1578 closed [x] / [~] (80%) - 322 still-open deferred (actionable backlog) - 74 wontfix (cosmetic, explicit close) - 0 items missing a *Status:* line The 322 actionable deferrals skew toward substantive backlog: missing tests, missing input validation, perf hot paths, refactor candidates, and spec/architecture drift. The full list of "real engineering work worth a follow-up pass" surfaced by Passes 1-3 is preserved in each review file's *Status:* lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the substantive deferrals surfaced by the four-pass review-backlog sweep into a single planning doc, formatted to match specs/tasks/M*/TASK-*.md so each entry can be split into its own task file when work starts. - TASK-053 Wire lookup_v2() into dispatch hot path (L, GA-blocker) - TASK-054 Migrate auth_handler_ptr to optional<http_response> (M, GA-blocker) - TASK-055 DR-009 revision: default error body must not surface e.what() (M, GA-blocker, CWE-209) - TASK-056 Hash-DoS hardening + prefix-route disambiguation (M, GA-blocker, CWE-407) - TASK-057 Redact credentials in http_request::operator<< (S, GA-blocker, A09:2021) - TASK-058 Hot-path allocation pass (L, post-v2.0 polish) - TASK-059 sha256-pin PMD analyzer in CI (S, GA-blocker) Each entry has: goal, action items, dependencies, acceptance criteria, related findings (back-references to specs/unworked_review_issues/), and related decisions. Execution-order section recommends a 3-4 week sequencing for a single engineer with TASK-057 and TASK-059 as Friday-afternoon warm-ups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls in two commits authored on the orphan fix/task-007-review-cleanup worktree at /private/tmp/task-007-review-cleanup that the Pass 1 review sweeper flagged as missing from feature/v2.0: - 477a06f TASK-007 review cleanup: address cosmetic and minor behavioral findings (Makefile.am hygiene comments + sed-vs-awk + .PHONY, verify-build YAML, TASK-007/TASK-020 AC grep pattern, header_hygiene_test MSYS2 guard). - dff19e5 TASK-007 review: broaden HYGIENE_STAMP deps to include Makefile.am and consumer TU. Both commits were authored 2026-05-27 by Sebastiano Merlino with Claude Sonnet 4.6 — the user's own work, just stranded on the fix branch. # Conflicts: # Makefile.am
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integration branch for the v2.0 modernization effort. Tasks land here individually (one merge commit per task) so the full v2.0 ships as a single reviewable PR.
This PR will remain draft until all milestones are complete.
Milestones
Specs live under
specs/(product_specs, architecture, tasks).Merged tasks
Test plan
Per-task validation runs through the groundwork validation loop on each task branch before merging here. Pre-merge of v2.0 to
master:./configure && makeclean on macOS (Apple Clang) and Linux (recent GCC)make checkgreen-std=c++(11|14|17)regressions in tree🤖 Generated with Claude Code