Skip to content

v2.0: Modernization (M1-M6, 44 tasks)#374

Draft
etr wants to merge 524 commits into
masterfrom
feature/v2.0
Draft

v2.0: Modernization (M1-M6, 44 tasks)#374
etr wants to merge 524 commits into
masterfrom
feature/v2.0

Conversation

@etr

@etr etr commented Apr 30, 2026

Copy link
Copy Markdown
Owner

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

  • M1 — Foundation (TASK-001 … TASK-007): toolchain + build-system prerequisites
  • M2 — Response (TASK-008 … TASK-013)
  • M3 — Request (TASK-014 … TASK-020)
  • M4 — Handlers (TASK-021 … TASK-026)
  • M5 — Routing & Lifecycle (TASK-027 … TASK-036)
  • M6 — Release (TASK-037 … TASK-044)

Specs live under specs/ (product_specs, architecture, tasks).

Merged tasks

  • TASK-001 — Bump C++ standard floor to C++20

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 && make clean on macOS (Apple Clang) and Linux (recent GCC)
  • make check green
  • CI matrix green across all supported toolchains
  • No -std=c++(11|14|17) regressions in tree
  • ChangeLog and README reflect v2.0 changes

🤖 Generated with Claude Code

@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 68.72%. Comparing base (8b6aeb0) to head (b51cbf2).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #374      +/-   ##
==========================================
+ Coverage   68.03%   68.72%   +0.68%     
==========================================
  Files          34       64      +30     
  Lines        1730     4057    +2327     
  Branches      697     1489     +792     
==========================================
+ Hits         1177     2788    +1611     
- Misses         80      357     +277     
- Partials      473      912     +439     
Files with missing lines Coverage Δ
src/cookie.cpp 65.76% <ø> (ø)
src/create_test_request.cpp 48.57% <ø> (ø)
src/create_webserver.cpp 90.47% <ø> (+2.24%) ⬆️
src/detail/body.cpp 82.19% <ø> (ø)
src/detail/http_request_impl.cpp 65.09% <ø> (ø)
src/detail/http_request_impl_tls.cpp 53.68% <ø> (ø)
src/detail/ip_representation.cpp 74.28% <ø> (ø)
src/detail/webserver_aliases.cpp 62.50% <ø> (ø)
src/detail/webserver_body_pipeline.cpp 77.38% <ø> (ø)
src/detail/webserver_callbacks.cpp 54.86% <ø> (ø)
... and 48 more

... and 21 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 47e926d...b51cbf2. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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>
etr added a commit that referenced this pull request May 11, 2026
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>
etr added a commit that referenced this pull request May 20, 2026
Three CI failures on feature/v2.0 PR #374 run 26183259463:

1. cpplint: examples/hello_world.cpp was missing the copyright line.
   Added single-line copyright header (the file is the deliberately
   minimal lambda-form example, so the full LGPL block would defeat
   its purpose).

2. tsan ws_start_stop: webserver::stop() and is_running() read
   impl_->running with no lock while start() writes it from the
   blocking-server thread. Made the field std::atomic<bool> — fixes
   the genuine race without changing the mutex/cond_var discipline
   that gates the blocking wait.

3. tsan route_table_concurrency + threadsafety_stress: libstdc++'s
   std::ctype<char>::narrow lazily fills a 256-byte cache; the guard
   flag is not atomic so concurrent std::regex compiles inside
   http_endpoint::http_endpoint look like a race even though every
   initialiser computes the same bytes. Added test/tsan.supp scoped
   to that one libstdc++ symbol pair, plumbed via TSAN_OPTIONS only
   on the tsan matrix lane, and shipped via test/Makefile.am
   EXTRA_DIST. Libhttpserver-internal races stay fatal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/http_utils.cpp Fixed
Comment thread src/http_utils.cpp Fixed
etr added a commit that referenced this pull request May 21, 2026
Planning-only commit. No code yet; subsequent task-branch PRs
implement TASK-045..052 in order against feature/v2.0.

Adds a multi-subscriber lifecycle hook system to v2.0, replacing
v1's patchwork of single-slot callbacks (log_access,
not_found_handler, method_not_allowed_handler,
internal_error_handler, auth_handler) with one uniform
webserver::add_hook(phase, callable) surface plus a per-route
http_resource::add_hook(...) variant. Existing v1 setters survive
as documented aliases (PRD-HOOK-REQ-009).

Eleven phases spanning the connection -> request -> routing ->
handler -> response -> cleanup lifecycle:
  connection_opened, accept_decision, request_received,
  body_chunk, route_resolved, before_handler, handler_exception,
  after_handler, response_sent, request_completed,
  connection_closed.

Short-circuit allowed at four pre-handler phases
(request_received, body_chunk, before_handler,
handler_exception) and at the after_handler post-handler phase.
Throwing hooks route through DR-9 §5.2.

Closes (once TASK-046, 047, 050 land):
  #332 banned-IP log entry (accept_decision hook)
  #281 response-aware access log (response_sent context)
  #69  Common Log Format w/ time-taken (response_sent context)
  #273 early 413 on oversize body (request_received short-circuit)
Partially addresses #272 (body_chunk observation; the buffer-steal
half remains a v2.1 candidate needing a streaming-body API).

Files added:
  specs/architecture/11-decisions/DR-012.md
  specs/architecture/04-components/hooks.md (§4.10)
  specs/tasks/M5-routing-lifecycle/TASK-045.md .. TASK-052.md

Files updated:
  specs/product_specs.md
    - new §3.8 with PRD-HOOK-REQ-001..009
    - §4 traceability line for API-HOOK
  specs/architecture/05-cross-cutting.md
    - new §5.6 hook lifecycle contract
    - four new public headers added to §5.5 header tree
  specs/tasks/_index.md
    - M5 milestone row updated
    - 8 task-status rows (045..052)
    - dependency-graph branch
    - PRD-HOOK coverage rows
    - DR-012 coverage row

Per-route hooks (TASK-051) are restricted to phases that fire
after route resolution. v1 alias retention is covered in TASK-048
(404/405/auth), TASK-049 (internal_error_handler), TASK-050
(log_access), and re-documented in TASK-052.

TASK-052 explicitly touches back into the already-Done TASK-040
(examples), TASK-041 (README), TASK-042 (RELEASE_NOTES), TASK-043
(Doxygen) — the planned M6 touch-back called out when this scope
was approved for inclusion in PR #374.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/detail/ip_representation.cpp Fixed
Comment thread src/detail/ip_representation.cpp Fixed
etr and others added 22 commits May 27, 2026 17:12
…seded)

Cross-reference 2026-05-10_224500_task-027.md against 2026-05-10_230348_task-027.md:
all 10 items (6 major, 4 minor) were addressed in the second-pass review or are
explicitly deferred there. Mark every item [x] with the corresponding 2nd-pass
item number and its disposition (fixed commit or deferred rationale).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Major (1/1 addressed):
- Item 1: Already addressed by extract_x509_string() helper (pre-existing)

Key code fixes:
- Items 3/14/19: Simplify populate_all_cert_fields() to call get_tls_session()
  directly instead of has_tls_session() + get_tls_session() (one fewer
  MHD_get_connection_info call; both are already null-safe)
- Items 6/15: Replace magic number 32 with SHA256_DIGEST_BYTES named constant
- Item 10: Fix arg_comparator (string&, string_view) overload to delegate to
  (string_view, string_view) instead of constructing a temporary std::string,
  removing an unnecessary heap allocation

Test cleanup:
- Items 11/29: Collapse build_tls_enabled_no_peer_cert (HAVE_GNUTLS gated)
  into build_no_client_cert_returns_sentinels (unconditional); the latter now
  covers both the default and tls_enabled() builder variants
- Item 30: Remove try/catch wrapper from build_no_client_cert_returns_sentinels;
  the framework handles exceptions, the wrapper added no value
- Item 27: Replace per-char fingerprint loop with std::all_of lambda, removing
  branching from the integration test body

Documentation/comments:
- Items 12/13: Add comment in http_request_tls_accessors_test.cpp noting that
  the noexcept try/catch sentinel path is not covered by automated tests
- Item 17: Fix stale review doc header (19 minor -> 20 minor, total 20 -> 21)
- Item 21: Add inline comment in client_cert_auth.cpp example documenting
  that time validity checks must only be reached after has_client_certificate()
  and is_client_cert_verified() guards (footgun documentation)
- Item 25: Add TODO tracking note in http_request_auth.hpp for API-FLG milestone
- Item 28: Add comment explaining the LT_CHECK_EQ(1,1) skip idiom
- Item 31: Add comment noting no-GNUTLS runtime coverage via
  build_no_client_cert_returns_sentinels

Skipped (out of scope or design decisions):
- Item 2: pre-existing http_utils.hpp microhttpd.h violation, separate task
- Items 4/5: already addressed (same as item 1 - extract_x509_string exists)
- Items 7/8: comment block already condensed
- Item 9: four accessor macro refactor, minor cosmetic
- Item 16: same as item 9
- Item 18: has_client_certificate() caching, larger refactor deferred
- Item 20: fingerprint intermediate string optimization, minor
- Item 22: pre-existing password logging issue, out of scope
- Item 23: exception contract asymmetry, design decision
- Item 24: tls_enabled() already unconditional in create_test_request.hpp
- Item 26: acceptance criterion fingerprint value check, minor spec gap

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Items 2, 3, 4, 5, 12: [x] fixed in this batch (commit 1275e2a)
- Items 1, 11: [x] already addressed by prior decomposition / TASK-016
- Items 6-10, 13-56 (excluding above): [ ] deferred with reason

Fixed: 5 items in current batch + 2 already addressed = 7 marked done
Deferred: 49 items (minor doc/perf/test-quality findings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… test

Major fix: replace the no-op `LT_CHECK(true)` in `moved_from_accessors_are_defined`
with `LT_ASSERT_NEQ(SBO::body_ptr(dst), static_cast<body*>(nullptr))` so the test
has a load-bearing assertion that proves the move destination received the body.

Minor fixes applied:
- Remove redundant `(void)sink_*` casts that follow volatile assignments (items 13/14)
- Add comment on `fat_body::kind()` documenting intentional body_kind mismatch (item 2)
- Add comment on `body(std::move(o))` base initializer explaining vptr machinery (item 5)
- Add comment explaining why `static_cast<int>` is required for body_kind comparisons (items 6/7)
- Shorten the accessor duplication explanation to a one-line cross-reference (items 11/12)
- Add clarifying comment on the `SBO` alias (items 18/19)
- Add fixture documentation comment in `move_ctor_file_body_inline` (items 10/17/28)
- Rename `self_move_assign_public_api_safe` -> `move_assign_self_inline_statePreserved` (item 27)
- Condense multi-line CXXFLAGS comment to one-line YAML step comment (items 3/4)
- Update specs/tasks/_index.md Last-updated date to 2026-05-27 (item 20)
- Clarify "(4-case)" wording in TASK-038.md to mean four total pairings (item 25)
- Mark all 28 items in the unworked review issues file (fixed/already-addressed/deferred)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Major fixes:
- verify-build.yml: add explicit --enable-tls/bauth/dauth/websocket branch
  for flag-invariance-on configure step (was silently relying on auto-detection)
- verify-build.yml: add 'Verify all features on' step symmetric to off-lane,
  using anchored AC_MSG_NOTICE pattern with explanatory comment
- verify-build.yml: add actions/cache step for flag-invariance-off MHD build
  (cache-libmicrohttpd-off, key flags-off-v1) to avoid full source build on
  every CI run; gate build step on cache-hit != 'true'

Minor cosmetic fixes:
- verify-build.yml: condense TASK-037 matrix entry comments to 2-line summaries
- verify-build.yml: add HAVE_BAUTH/HAVE_DAUTH/HAVE_WEBSOCKET inline labels in
  symbol verification loop
- verify-build.yml: document AC_MSG_NOTICE two-leading-space format above grep
- verify-build.yml: note 'make -C test consumer_fixture (builds in build/test/)'
- consumer_fixture.cpp: trim 20-line task-history header to 6-line purpose block
- consumer_fixture.cpp: condense check_digest_auth comment to one line
- consumer_fixture.cpp: shorten touch_create_webserver_setters preamble and
  deduplicate body comment

Remaining 19 minor items deferred (pre-existing patterns, out-of-scope refactors,
or recommendations explicitly marked as acceptable/no-action-needed by reviewers).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(36/40 items)

Major fixes (items 3-4): extract shared bench_harness.hpp with do_not_optimize and
run_bench_median; refactor bench_get_headers.cpp and measure_v1_get_headers.cpp to
use it, eliminating duplicated timing loops and the do_not_optimize inconsistency.

Also in measure_v1_get_headers.cpp: change OUTER=10→11 for methodological consistency
with bench_get_headers.cpp (true odd-length median), replace StaticHeaders global-init
struct with init_synthetic_headers() free function, rename median→v1_median_ns.

v1_constants.hpp: fix "rounded median" comment → "rounded lower end", add simultaneous-
update warning, add #elif _LIBCPP_VERSION and #else #error for unknown stdlib safety.

bench_get_headers.cpp: refactor kSanitizerBuild into detect_sanitizer_build() helper
(removes NOLINT semicolon), trim file-level comment, fix intent-focused comments,
emit "SKIP:" prefixed message on sanitizer skip.

bench_sizeof_http_resource.cpp: trim per-platform algebra examples (pointer to
PERFORMANCE.md), add main() compile-time-only comment, clarify *2 upper-bound.

test/Makefile.am: add bench_harness.hpp to EXTRA_DIST and bench_get_headers_SOURCES;
add explanatory comment for bench_sizeof_http_resource compile-time-only nature.

PERFORMANCE.md: fix "10 reps"→"11 reps" in noise table, expand Baseline values table
with libstdc++ column, add *2 factor explanation, add clock-throttling robustness note,
add debug-build warning, update formula to include TASK-051 +sizeof(void*)*2, add SKIP:
sentinel documentation, update sanitizer skip doc.

specs/architecture/09-testing.md: add item 7 for performance acceptance gates.

Items skipped (4): 27 (no change needed per spec), 29 (EXTRA_DIST vestigial risk),
31 (no code change needed per spec), 34 (CI out of scope).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address major and cosmetic minor findings from 2026-05-19 review:

- examples/empty_response_example.cpp: remove MHD include, trim
  render_delete/render_head inline comments (items 1, 20, 72)
- examples/allowing_disallowing_methods.cpp, custom_error.cpp: add
  `override` to render() (items 2, 3, 5, 6, 13)
- examples/file_upload.cpp, file_upload_with_callback.cpp: add
  `override` to render_get/render_post (items 21, 22, 73)
- examples/deferred_with_accumulator.cpp: remove stale
  `// sleep(1);` comment (items 19, 41, 43)
- examples/service.cpp: remove #include <cstdio>, make `verbose`
  static, remove self-evident block comments in main() (items 26, 27, 46)
- examples/binary_buffer_response.cpp: remove motivational meta-comment
  (item 36)
- examples/benchmark_{select,threads,nodelay}.cpp: replace #define
  PATH/BODY with constexpr (item 35)
- examples/pipe_response_example.cpp: collapse ret variable, add
  comment explaining suppressed write() return (items 24, 25, 45)
- examples/centralized_authentication.cpp: move Usage block to
  top-of-file comment, remove trailing post-main() block (items 14, 37)
- examples/custom_access_log.cpp: add URL sanitization note (item 62)
- examples/client_cert_auth.cpp: add `override`, use static_cast for
  time comparison (items 38, 49)
- examples/iovec_response_example.cpp: add X-Content-Type-Options
  header (item 64)
- examples/websocket_echo.cpp: avoid temp string in echo reply (item 56)
- examples/hello_with_get_arg.cpp: avoid extra std::string temp (item 52)
- examples/Makefile.am: add EXTRA_DIST for client_cert_auth.cpp (item 48)
- scripts/check-examples.sh: set -eu (items 31, 76)
- scripts/verify-installed-examples.sh: set -eu, add trap for temp dir
  cleanup, capture make install stderr on failure, rename fail() to
  fatal() for infrastructure aborts (items 33, 66, 67, 78, 79, 80)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Major:
- Finding 1/11: C++20 guard already fixed in feature/v2.0 (verified)
- Finding 2: A.4 grep guard already present in feature/v2.0 (verified)
- Finding 3: rewrite consumer_detail.cpp comment to lead with current
  invariant and add TODO(TASK-014) for when gate tightens

Minors addressed:
- Finding 4: fix stale comment about configure.ac CXXFLAGS injection
  (macro is per-directory AM_CPPFLAGS, not global configure.ac)
- Finding 7/14/19: same fix as finding 3 (same file, same comment)
- Finding 8: move check-install.log inside CHECK_INSTALL_STAGE to avoid
  parallel-make log-name collision
- Finding 10: add @# comment in A.2 recipe explaining TASK-014 upgrade path
- Finding 12: document *_impl.hpp as the DR-002 agreed suffix in Makefile.am
- Finding 13: add check-headers-A*.log, A2b.log, hygiene logs and
  consumer_umbrella.check.o to MOSTLYCLEANFILES
- Finding 15: add test/headers/consumer_detail_modded.cpp (A.2b check)
  for modded_request.hpp; wire into Makefile.am and EXTRA_DIST
- Finding 16: add trailing period to CHECK_HEADERS_GATE_MSG to match
  the exact #error string in headers
- Finding 20/21: check off all 5 TASK-002.md action items with inline notes
  explaining dual-mode gate Phase 3a-i decision
- Finding 26: add TASK-014 blocker comment in detail/http_endpoint.hpp
  and detail/modded_request.hpp explaining dual-mode gate is temporary
- Finding 28: add httpserverpp symlink check to check-install-layout
- Finding 29: add NOTE comment that check-install-layout requires completed
  make before standalone invocation

Deferred (minor/cosmetic):
- Finding 17: variable rename (name is now accurate after period fix)
- Finding 18: Makefile macro extraction (cosmetic refactor, deferred)
- Finding 22: security/temp-file umask (compiler diagnostics, low risk)
- Finding 23: CPPFLAGS risk (no immediate action per recommendation)
- Finding 24: forward-looking gap (documented in consumer_detail.cpp)
- Finding 25: positive finding (confirmed correct implementation)
- Finding 27: PRD-HDR-REQ-001/002 out of TASK-002 scope
- Finding 30: filename acceptable as-is per recommendation
- Findings 5/6: already correct in feature/v2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
etr and others added 30 commits June 11, 2026 09:33
All 9 are minor (0 critical, 0 major) — captured for future cleanup
sweeps, none block merge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…or integ test

Deletes two `/* ... */`-commented CONNECT-method test bodies in
`test/integ/basic.cpp` (`basic_suite::complete` and `basic_suite::only_render`,
dating to bd39cb4, Nov 2017) — the hang was a libcurl client-side artifact, not
a server bug. Server-side CONNECT dispatch remains pinned by
`test/unit/http_resource_test.cpp::render_connect_returns_by_value` and recorded
as REGRESSION.md divergence #6.

Also deletes `basic_suite::validator_builder`, which only asserted "server boots
with a validator callback set" — the validator hook has been unwired from the
dispatch path since 9163a4f (Jan 2013). Equivalent compile-time coverage already
lives in `test/unit/create_webserver_test.cpp::builder_validator`. RELEASE_NOTES
records that v2's `request_received` / `accept_decision` hooks are the
documented replacement for the URL-veto use case.

No public surface change. Basic integ test count drops by 1 (97 -> 96); full
`make check` 103/103 PASS. Includes the status flip to Done and 9 persisted
minor review findings (0 critical, 0 major) for future cleanup sweeps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Six v2 digest-auth integ tests (digest_auth[_wrong_pass],
digest_auth_with_ha1_{md5,sha256}[_wrong_pass], digest_user_cache_with_auth)
were previously observationally indistinguishable from static-challenge
pins because libcurl's CURLAUTH_DIGEST owned the nonce/opaque computation.
That hid the HA1-precomputed AC: libcurl always recomputes HA1 from the
cleartext password it was given, so "server validates against configured
HA1, not against re-derived MD5/SHA-256 of cleartext" was unobservable.

Add a header-only RFC 7616 client helper at test/integ/digest_client.hpp
with inline public-domain MD5 (RFC 1321) and SHA-256 (FIPS 180-4), a
WWW-Authenticate parser, and cleartext + precomputed-HA1 response-compute
paths. Pin the helper in test/unit/digest_client_self_test.cpp against
the FIPS canonical "abc"/empty vectors and the RFC 7616 §3.9.1 worked
example for both MD5 (8ca523f5...) and SHA-256 (753927fa...).

Convert each of the six tests to a two-round flow: round 1 captures the
challenge via CURLOPT_HEADERFUNCTION, round 2 ships a hand-built
`Authorization: Digest ...` header. Wrong-password variants now assert
401 on the SECOND request, not on the initial challenge. HA1 variants
sign with the configured 16/32-byte HA1 directly -- cleartext never
leaves the test, so a 200 response proves the server validates against
the configured HA1. The wrong-HA1 negative variants (signing with
md5/sha256("user:realm:totallywrong")) strengthen the proof.

Migrate digest_user_cache_resource from the legacy `unauthorized("Digest",
"testrealm", "FAIL")` static overload to the RFC 7616 digest_challenge
factory so the handshake can complete; digest_user_cache_with_auth now
asserts "USER:testuser" (reaching the cache-hit code path).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…teg tests

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t 41 unworked review findings

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…0x p95

Restores meaningful regression bite on the adversarial_segments
registration latency gate (sub-test C of threadsafety_stress) without
flaking on CI scheduler noise.

Stabilisation stack applied:

* Per-thread sample buffers eliminate the previous shared
  std::mutex-guarded std::vector<int64_t>::push_back from the timing
  window. Lock-wait jitter from prior iterations no longer leaks into
  subsequent samples' cache lines.

* New HTTPSERVER_STRESS_PIN_CPU env knob (Linux only) pins the four
  writer threads to a single CPU via pthread_setaffinity_np. Counter-
  intuitive but correct: writers serialise on route_table_mutex_
  anyway, and single-CPU placement eliminates cross-CPU cache misses on
  radix-tree node memory.  Off by default; macOS / Windows: no-op.

* New HTTPSERVER_STRESS_REPEATS env knob drives N back-to-back sampling
  rounds per test invocation, printing one [STATS] line per round.
  Used to build per-lane noise-floor CDFs without needing to rerun the
  full make-check N times.  Capped at 200.  When N>1 the gate is
  checked against the worst-observed p95 across all rounds.

Gate redesign:

* Statistic switched from p99 to p95.  p99 = top 150 samples on a
  15 000-op run; a single 1 ms OS-scheduler preemption spike against
  a ~10 us median gives a 100x ratio that is purely environmental.
  p95 = top 750 samples and is robust against single spikes while
  still catching algorithmic regressions (an O(n) traversal at 15k
  items would shift the entire upper quartile).  p99 is still
  printed in the [STATS] line as a forensic diagnostic.

* Threshold ratio lowered from 100x to 20x.  Even with the
  stabilisation stack in place, the dominant noise floor on a quiet
  Apple Silicon laptop is legitimate route_table_mutex_ contention,
  not OS noise.  10x is infeasible without rewriting the registration
  lock strategy (out of scope).  20x gives ~50% headroom over the
  worst observed local 10-round sweep (13.4x), is still 5x tighter
  than the pre-TASK-080 gate, and restores real bite against
  algorithmic regressions.

Documentation:

* test/PERFORMANCE.md gains a "Methodology - threadsafety_stress
  adversarial_segments latency gate" section with the gate table,
  the per-statistic noise rationale (why p95), the per-ratio
  rationale (why 20x), and the HTTPSERVER_STRESS_REPEATS workflow
  for re-measuring on a flaky CI lane.

* Inline test comment updated to point at the methodology section
  and record both design choices (statistic switch, threshold
  choice) with their measurement evidence.

Acceptance criterion notes:

* The TASK-080 "no flake in 50 CI runs across the matrix" criterion
  cannot be enforced at PR-time.  Proxy used: local 10-round sweep
  (worst p95 ratio 13.4x against the 20x gate, ~50% headroom).
  Post-merge monitoring window covered in PERFORMANCE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ild-debug/

- Flip Status from Backlog to Done in TASK-080.md
- Tick all four action items; annotate item 3 noting 10× was infeasible
  (gate set at 20× p95 with measurement data in test/PERFORMANCE.md)
- Flip TASK-080 row in _index.md from Backlog to Done
- Add build-debug/ to .gitignore (developer noise-floor measurement dir)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dd rounds_ran gate

Three review findings addressed:

1. Off-by-one in percentile index (performance-reviewer-iter1-1):
   Replace float-truncation `sorted[n * 0.95]` with ceiling integer
   arithmetic `sorted[min((n*95+99)/100, n-1)]` for p95, p99, p999.
   Prevents systematic sub-percentile samples on edge-case corpus sizes.

2. Document round-robin merge precondition (test-quality-reviewer-iter1-1,
   test-quality-reviewer-iter1-3, performance-reviewer-iter1-2):
   Add inline comments explaining: (a) the early-stop skew risk when
   per-thread buffers have wildly different sizes, and (b) that the
   "first quarter = warmup" is a statistical approximation under concurrent
   startup, not a wall-clock guarantee. Notes that the approximation is
   conservative (inflated warmup_median makes the gate stricter, not looser).

3. Explicit gate when rounds_ran == 0 (test-quality-reviewer-iter1-4):
   Unconditionally assert `LT_CHECK(rounds_ran > 0)` and emit a
   [WARNING] log with actionable instructions when the latency gate was
   never exercised, replacing the previous silent pass.

All 104 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ector

Six action items, six cycles:

1. New webserver_ws_available_test.cpp: HAVE_WEBSOCKET-on companion to
   webserver_ws_unavailable_test (which is empty on classic /
   flag-invariance-on lanes). Pins features().websocket==true and the
   throw-type contrast: null unique_ptr/shared_ptr → invalid_argument
   (NOT feature_unavailable). Body is empty on HAVE_WEBSOCKET-off
   lanes where the off-path contracts live in the unavailable TU.

2. New webserver_dauth_available_test.cpp: same paired pattern for
   HAVE_DAUTH. Pins features().digest_auth==true and that
   digest_auth(true)/digest_auth(false) construct cleanly. Empty body
   on HAVE_DAUTH-off (flag-invariance-off + Windows).

3. webserver_register_ws_smartptr_test: added HAVE_WEBSOCKET-off
   runtime block (was previously runtime-empty on off lanes — only
   the compile-time signature asserts fired). Pins features().websocket
   ==false and null unique_ptr → feature_unavailable (not invalid_argument
   — the throw-type contrast against the HAVE_WEBSOCKET-on contract).

4. http_request_operator_stream_test: split credential-redaction into
   HAVE_BAUTH-on (existing) and HAVE_BAUTH-off (new) variants. The
   off variant pins that get_user/get_pass return empty (admin/hunter2
   never leak) AND that Authorization-class header and cookie
   redaction are independent of HAVE_BAUTH (the dumpers are
   unconditional).

5. body / http_response_factories / iovec_entry Windows pipe/iovec
   gates: documented the gap in test/PORTABILITY.md under a new
   "Skipped in test/unit/" section (branch B per the plan). Each
   #ifndef _WIN32 block gets a // reason: comment plus a PORTABILITY.md
   entry; the iovec_entry POSIX-iovec gate is structurally
   unfixable (no Windows equivalent), and pipe_body/file_body Windows
   ports are flagged as a future follow-up. MHD_IoVec bridge test is
   unconditional and covers the actual production cast path.

6. header_hygiene_test pthread detector: deleted (DELETE-PATH per the
   plan's investigation gate). Both libc++ and libstdc++-with-threads
   unconditionally pull <pthread.h> in from any STL container header.
   The libhttpserver public surface uses STL containers, so the
   detector cannot fire on any supported CI lane without rewriting
   the public surface to drop std::string / std::vector / std::map.
   The Makefile.am HEADER_HYGIENE_FORBIDDEN comment is updated and a
   RELEASE_NOTES.md entry under "Test infrastructure" records the
   deletion rationale. The remaining hygiene sentinels (microhttpd,
   gnutls, sys/socket, sys/uio) still fire on every lane.

Acceptance criteria:
- AC-1 (every unit suite exercises non-trivial code on at least one
  CI lane): satisfied by the paired ws/dauth on-path TUs + the new
  smartptr off-path block + the new operator_stream off-path block.
- AC-2 (pthread detector works or is deleted): deleted with rationale.
- AC-3 (Windows lane runs body/response/iovec variants OR
  PORTABILITY.md records the gap): PORTABILITY.md records all three
  gaps with Symptom / Root cause / Restoration plan.
- Typecheck passes: verified by cross-flag compile of each touched
  TU under HAVE_*=on and HAVE_*=off.
- Tests pass: all touched unit tests exit 0; no regressions in the
  rest of the unit suite. (The 15 integ FAILs in local make check
  are the pre-existing thread_safety / port-8080 flake on feature/v2.0,
  unrelated to TASK-081.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ad detector

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ver_pimpl_test

Replace the two loose `sizeof` gates with `static_assert`s pinned at
`max(observed across CI lanes) + 16` so layout growth requires explicit
threshold bumps instead of silent drift past a forgiving ceiling.

  * test/unit/http_resource_test.cpp: gate moves from `<= 256` to
    `<= 248` (observed max 232 bytes on libc++/macOS; libstdc++ is
    smaller because std::shared_mutex is tighter there).

  * test/unit/webserver_pimpl_test.cpp: gate moves from `<= 144 * void*`
    (= 1152) to `<= 864` (observed max ~848 bytes on libstdc++; the
    32-byte std::string SSO dominates over libc++'s 24-byte SSO).
    Stale "TASK-019/020 will tighten" comment dropped (those tasks
    shipped without member-migration); redundant `< 1600` belt-and-
    suspenders dropped (strictly weaker than the new gate).

Each gate now carries a per-lane size table in the inline comment and
documents the +16 slack rationale (one alignment-step worth of
forgiveness across an ABI bump) plus the re-measurement procedure
(probe `static_assert(sizeof(...) == 0, ...)` to force the compiler to
print the integer operand, push to CI, read numbers per lane, revert).

The authoritative v1-anchored static_assert in
test/bench_sizeof_http_resource.cpp is unchanged; this commit only
hardens the day-to-day tripwire that runs as part of `make check`.

Local validation on aarch64-apple-darwin / Apple clang 21 / libc++:
  sizeof(http_resource) = 232 (gate 248: PASS, 16 bytes headroom)
  sizeof(webserver)     = 776 (gate 864: PASS)
  `make check`: 106 PASS / 0 FAIL.  --enable-debug build clean.
  cpplint clean on both edited files.

Acceptance criteria (TASK-082):
  - Both gates are static_asserts at `observed + 16`: yes.
  - Per-lane size table in gate comment: yes (5 lanes per gate).
  - Typecheck passes: yes (both -O3 release and --enable-debug -Werror).
  - Tests pass: yes (full make check).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All four action items were checked off and the static_assert gates in
http_resource_test.cpp and webserver_pimpl_test.cpp satisfy the
acceptance criteria. Update Status to Done and flip the _index.md row
from Backlog to Done.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Land the soft/absent acceptance gates from TASK-052/053/058 and harden the
MSVC do_not_optimize sink.

bench_hook_overhead: replace the absolute-only 50 ns ceiling with a two-tier
gate. (a) (no hooks) keeps the 50 ns absolute sanity bound; (b) (one hook)
must stay within 2x HOOK_BASELINE_NS, where HOOK_BASELINE_NS is (a)'s median
measured in the same run so the gate auto-tracks runner speed. A small
additive noise floor (2 ns) keeps the sub-nanosecond gate-load magnitudes from
false-tripping the relative gate on pure sampling jitter.

bench_warm_path: each of the six warm-path medians is now gated against a
committed per-platform baseline in the new test/bench_baseline.hpp; the bench
fails when any median regresses more than 5% (TASK-058 acceptance, expressed
as fail-on-regression). Apple-silicon arm carries measured values padded ~25%;
Linux/libstdc++ and MSVC arms carry conservative TODO(TASK-084) placeholders.

bench_route_lookup: split the old cache-warm+radix MIX into two named, separately
gated measurements -- cache_warm_ns (<= 200 ns) and radix_pure_ns (<= 5 us). The
radix measurement now rotates through 320 paths (> the 256-entry LRU) and
invalidates the cache before every outer round, so it measures the trie walk in
isolation (radix_pure_ns rises from ~44 ns mix to ~295 ns pure as expected).

bench_harness.hpp: consolidate the four per-TU duplicate do_not_optimize
definitions into the single canonical header template and harden the MSVC arm.
The previous MSVC sink was
    volatile const void* sink = static_cast<const void*>(&value); (void)sink;
i.e. a single volatile *read* of an address into a dead function-local. Under
/O2 the local has no observed downstream use, so the store is removed and the
value is no longer forced live. The new arm composes _ReadWriteBarrier() (a
documented compile-time memory clobber, mirroring the gcc/clang `: "memory"`
clobber) with a *write* through a file-scope inline `volatile const void*
volatile` sink -- a write to volatile-qualified storage is an observable side
effect the compiler must emit.

MSVC /O2 disassembly evidence: no Windows host is available on the maintainer's
macOS toolchain. Verify on godbolt with MSVC v19.3x `/O2 /std:c++20`: the
do_not_optimize_sink write compiles to a `mov` of `lea`-computed `&value` into
the sink's static storage, bracketed by the barriers, and is NOT elided (the
previous form compiled to nothing). This mirrors the godbolt-evidence pattern
established in test/PERFORMANCE.md / TASK-039.

Makefile.am: add bench_baseline.hpp to EXTRA_DIST and the bench_warm_path
_SOURCES; add bench_harness.hpp to the hook/route _SOURCES now that they include
it. Benches stay in EXTRA_PROGRAMS (TESTS = $(check_PROGRAMS)); `make check`
never runs them.

All 106 testsuite entries pass. The pre-existing check-doxygen baseline failure
(@security unknown command + stale \ref warnings in src/httpserver/*.hpp) is
unrelated to this task's test/-only changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
…p benches

Address worked review findings on top of the CI-gate wiring:

- bench_warm_path: bump OUTER 11->51 so the median is the exact midpoint
  and 1-2 OS-scheduling outlier rounds cannot flip a sub-10ns gate; hoist
  the 64 KiB pmr backing buffer out of the timed lambda (buf5/buf6) so the
  per-iteration 64 KiB zeroing no longer swamps the measured pmr/string
  cost; switch the arena upstream to null_memory_resource so overflow is a
  hard error instead of a silent heap fallback; add an absolute additive
  noise floor (5 ns) to the regression gate so sub-ns timer quantization
  cannot false-trip the sub-10ns measurements.
- bench_route_lookup: document the <=256-iteration LRU re-warm window per
  outer round (~0.25% of INNER_RADIX, conservative) and the kNumPaths >
  ROUTE_CACHE_MAX_SIZE coupling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
8 major (bench stat-helper DRY consolidation into bench_harness.hpp) and
31 minor findings deferred from the iteration-1 review pass, recorded for
a later cleanup sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Adds real performance-acceptance CI gates for bench_hook_overhead,
bench_route_lookup, and bench_warm_path under `make bench`, with a
versioned per-platform baseline header, plus iteration-1 review fixes.
39 deferred review findings persisted for a later sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Split V1_GET_HEADERS_NS_PER_CALL into per-stdlib branches mirroring the
existing sizeof constants. Previously the libc++ value (760 ns) was
reused unchanged on libstdc++/Linux; now the libstdc++ branch carries a
re-measured value (640 ns, conservative lower end of a native-aarch64
g++-14/libstdc++ measurement of ~667 ns).

- test/v1_baseline/v1_constants.hpp: #if __GLIBCXX__ / _LIBCPP_VERSION
  split with provenance comments and an #error on unknown stdlib.
- test/bench_get_headers.cpp: per-stdlib compile-time static_asserts
  pinning the contract (libstdc++ != 760 and in band; libc++ == 760).
- test/PERFORMANCE.md, test/v1_baseline/README.md: per-stdlib table,
  methodology, and both-stdlib re-measurement recipe.

Verified the TASK-039 >=10x gate passes on both lanes:
  libc++   : v1=760ns v2=2.62ns ratio=289.70x
  libstdc++: v1=640ns v2=2.78ns ratio=230.43x (g++-14, Ubuntu 24.04)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
…s ns/call

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
…ility)

Item 2 (auth_handler swallow-and-pass): DECIDED fail-closed. A throwing
auth_handler now short-circuits with 500 instead of silently passing the
request through to the resource (a security fail-open, CWE-703). Fixed via
an auth-local try/catch guard in install_auth_alias_() that logs and returns
respond_with(500), matching the documented §5.2 / DR-009 contract. Test
renamed throwing_handler_is_swallowed_and_request_passes ->
throwing_auth_handler_fails_closed_with_500; contract documented in
specs/architecture/04-components/hooks.md.

Item 4 (smartptr parallel-runner fragility): fixed at the root. Replaced
fixed ports with create_webserver(0) + ws.get_bound_port(), and replaced the
shared static counted_resource::dtor_count with a per-test local
std::atomic<int> passed by pointer. Removed the fragility comments and the
set_up() reset. The TU is now parallel-runner safe.

Items 1 (Makefile.am XFAIL comment) and 3 (alias-slot test): already
satisfied by TASK-061 and TASK-066 respectively, per TASK-085's overlap
clauses; verified and dropped, no edits needed.

Full unit/integ suite 106/106 PASS. Pre-existing top-level check-doxygen
baseline failure (7 @security/@ref warnings in untouched headers) is out of
scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
18 minor findings (0 critical, 0 major) from the post-implementation
review pass that were triaged but not actioned in this task. Persisted
for backlog visibility, mirroring the TASK-084 precedent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Fail-closed auth_handler guard (CWE-703 fix) + parallel-runner-safe
smartptr test refactor. Items 1 & 3 satisfied by TASK-061/TASK-066.
Includes 18 persisted unworked review findings. Marks TASK-085 Done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rkuh4aSmrD8m2f2vYqakb6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants