Skip to content

Commit c04bdb5

Browse files
etrclaude
andcommitted
review-sweep: work all unworked critical/major findings (tasks 060-094)
Sweeps the 51 unchecked major findings (0 critical) across the specs/unworked_review_issues/ ledger; every item is now [x] with a dated resolution note. Highlights: - src: overflow-safe bound in unescape_buf_raw; qop_auth_int now throws instead of silently no-oping; legacy with_cookie restores v1 overwrite wire semantics (single Set-Cookie); debug-dump wrapper collapsed into detail::debug_dump_request_body_opted_in - security follow-up: TASK-095 created to track CWE-226 arena-overflow zeroing residue; connection_state.hpp references it; residue integ test gains a black-box get_user() leak assertion - gates: new negative-compile gate for the [[deprecated]] cookie overload; server-ready-helper self-test wired into check-local; shared scripts/lib/find-matrix-includes.py dedupes the lane gates; codeql gate loop simplified to one grep -v pass - tests: LT_SKIP_IF(true,..)->LT_SKIP and curl_get() helper in ws_start_stop; stream_capture helpers and throw_probe.hpp extracted; threadsafety_stress CAS pool enables real same-slot contention and honest proxy docs; bench stat/timing helpers consolidated into bench_harness.hpp; new unescape, digest-factory and cookie-overwrite unit tests - docs: TASK-067 action items closed; TASK-080 50-run criterion marked deferred with Linux-sweep gap documented make check: 107/107 PASS (check-doxygen @security noise pre-existing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NpysYDDJac63yz2mZKKiDf
1 parent 52901b9 commit c04bdb5

57 files changed

Lines changed: 1063 additions & 948 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*.php
66
*.pm
77
*.py
8+
!scripts/lib/*.py
89
*_wrap.*
910
*.gcov
1011
*.gcno
@@ -16,6 +17,7 @@
1617
libhttpserver.iml
1718
build/*
1819
build-debug/
20+
_build/
1921
aclocal.m4
2022
autom4te.cache/
2123
config.guess

Makefile.am

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh s
5050
scripts/test_check_fence_balance.sh \
5151
scripts/lib/resolve-prefix.sh scripts/lib/skip-or-fail.sh \
5252
scripts/lib/check-fence-balance.sh scripts/lib/v1-no-setters.txt \
53+
scripts/lib/find-matrix-includes.py \
5354
scripts/check-complexity.sh scripts/check-duplication.sh \
5455
scripts/check-file-size.sh \
5556
scripts/check-warning-suppressions.sh \
5657
scripts/check-server-ready-helper.sh \
58+
scripts/test_check_server_ready_helper.sh \
5759
scripts/check-no-tautological-asserts.sh \
60+
scripts/check-deprecated-cookie-overload.sh \
5861
scripts/test_check_littletest_skip_exit_code.sh \
5962
scripts/verify-installed-examples.sh \
6063
test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_detail_modded.cpp \
@@ -369,7 +372,7 @@ check-hygiene:
369372
# staged the install; sub-check skips the install step to avoid double cost.
370373
# This knob is set only by check-local; do not set it when invoking sub-checks
371374
# standalone.
372-
check-local: check-headers check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck lint-warning-suppressions lint-server-ready-helper lint-no-tautological-asserts lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance
375+
check-local: check-headers check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck lint-warning-suppressions lint-server-ready-helper lint-server-ready-helper-selftest lint-no-tautological-asserts lint-deprecated-cookie-overload lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance
373376
@echo "=== Shared staged install for check-install-layout and check-hygiene ==="
374377
@rm -rf "$(abs_top_builddir)/.shared-check-stage"
375378
@$(MAKE) $(AM_MAKEFLAGS) install DESTDIR="$(abs_top_builddir)/.shared-check-stage" >check-shared-install.log 2>&1 || { \
@@ -487,6 +490,14 @@ lint-server-ready-helper:
487490
@echo "=== lint-server-ready-helper: forbid bare server-ready sleeps in hooks_* integ tests ==="
488491
@$(top_srcdir)/scripts/check-server-ready-helper.sh
489492

493+
# TASK-075: unit test for the server-ready helper gate itself. Runs the
494+
# gate against synthetic pass/fail fixtures so a regression in the
495+
# gate's detection logic is caught by `make check`, not just trusted.
496+
# Pure bash/awk/grep, runs in every CI lane.
497+
lint-server-ready-helper-selftest:
498+
@echo "=== lint-server-ready-helper-selftest: self-test the server-ready gate ==="
499+
@$(top_srcdir)/scripts/test_check_server_ready_helper.sh
500+
490501
# TASK-076: forbid `LT_CHECK_EQ(1, 1)` in test/integ/ws_start_stop.cpp.
491502
# Pre-TASK-076 ~33 catches in the TLS test suite used the tautological
492503
# assertion as a fake "skip" — a build that silently lost TLS support
@@ -499,6 +510,19 @@ lint-no-tautological-asserts:
499510
@echo "=== lint-no-tautological-asserts: forbid tautological pseudo-skip in ws_start_stop.cpp ==="
500511
@$(top_srcdir)/scripts/check-no-tautological-asserts.sh
501512

513+
# TASK-064 code review: negative-compile gate pinning the [[deprecated]]
514+
# attribute on the legacy string-blob overload
515+
# http_response::with_cookie(std::string, std::string). Compiles a TU
516+
# calling the deprecated overload with -Werror=deprecated-declarations
517+
# and asserts the compile FAILS (plus a positive control proving the
518+
# structured overload builds cleanly). Complements the positive-side pin
519+
# in test/unit/cookie_deprecation_sentinel_test.cpp. Wired into `make
520+
# check` via check-local; needs only a C++ compiler on $PATH (no build
521+
# tree or libmicrohttpd), so it runs in every lane.
522+
lint-deprecated-cookie-overload:
523+
@echo "=== lint-deprecated-cookie-overload: pin [[deprecated]] on legacy with_cookie(string, string) ==="
524+
@CXX="$(CXX)" $(top_srcdir)/scripts/check-deprecated-cookie-overload.sh
525+
502526
# TASK-076: pin the LT_SKIP exit-code semantics in test/littletest.hpp.
503527
# Asserts (a) an all-skip binary exits 77 (Automake SKIP), (b) a mixed
504528
# pass+skip binary exits 0 and emits both `[SKIP]` and `-> N skipped`
@@ -558,7 +582,7 @@ lint-workflow-pinning:
558582
@$(top_srcdir)/scripts/test_check_workflow_pinning.sh
559583
@$(top_srcdir)/scripts/check-workflow-pinning.sh
560584

561-
.PHONY: check-headers check-install-layout check-hygiene check-local check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck check-soversion check-parallel-install lint-complexity lint-duplication lint-file-size lint-warning-suppressions lint-server-ready-helper lint-no-tautological-asserts lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance lint-parallel-install-lane lint-codeql-workflow lint-workflow-pinning
585+
.PHONY: check-headers check-install-layout check-hygiene check-local check-examples check-readme check-release-notes check-doxygen check-hooks-doc-spotcheck check-soversion check-parallel-install lint-complexity lint-duplication lint-file-size lint-warning-suppressions lint-server-ready-helper lint-server-ready-helper-selftest lint-no-tautological-asserts lint-deprecated-cookie-overload lint-littletest-skip-exit-code lint-parallel-install-skip-contract lint-fence-balance lint-parallel-install-lane lint-codeql-workflow lint-workflow-pinning
562586

563587
# TASK-039: top-level convenience rule that descends into test/ to
564588
# build and run the bench binaries (see test/Makefile.am and

scripts/check-codeql-workflow.sh

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,11 @@ if [ -z "$uses_lines" ]; then
8989
echo " (c) no `uses:` action steps found in the workflow" >&2
9090
fail=1
9191
else
92-
bad_pins=""
93-
while IFS= read -r line; do
94-
[ -z "$line" ] && continue
95-
if ! printf '%s\n' "$line" | grep -qE '@[0-9a-f]{40}([[:space:]]|$)'; then
96-
bad_pins="$bad_pins\n $line"
97-
fi
98-
done <<< "$uses_lines"
92+
bad_pins="$(printf '%s\n' "$uses_lines" \
93+
| grep -vE '@[0-9a-f]{40}([[:space:]]|$)' || true)"
9994
if [ -n "$bad_pins" ]; then
10095
echo " (c) uses: line(s) not pinned to a 40-hex SHA:" >&2
101-
printf '%b\n' "$bad_pins" >&2
96+
printf '%s\n' "$bad_pins" | sed 's/^/ /' >&2
10297
fail=1
10398
else
10499
echo " (c) every uses: line pinned to a 40-hex commit SHA"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
#
3+
# check-deprecated-cookie-overload.sh — negative-compile gate pinning the
4+
# [[deprecated]] attribute on the legacy string-blob cookie overload
5+
# http_response::with_cookie(std::string, std::string) (TASK-064).
6+
#
7+
# test/unit/cookie_deprecation_sentinel_test.cpp pins the POSITIVE side
8+
# (the deprecated overload still compiles behind a #pragma suppression
9+
# block). The negative side — that the [[deprecated]] warning IS emitted
10+
# on a suppression-free compile — was previously unverified: if someone
11+
# removed the attribute, nothing would catch it (code-review finding on
12+
# TASK-064). This gate closes that hole:
13+
#
14+
# 1. Positive control: a TU calling the structured overload
15+
# with_cookie(cookie) MUST compile cleanly under
16+
# -Werror=deprecated-declarations. This proves a failure in step 2
17+
# comes from the deprecation, not from a broken header or
18+
# environment.
19+
# 2. Negative check: a TU calling the deprecated overload
20+
# with_cookie(std::string, std::string) MUST FAIL to compile under
21+
# -Werror=deprecated-declarations, and the compiler output must
22+
# mention "deprecated" (so an unrelated compile error cannot fake a
23+
# pass).
24+
#
25+
# Both TUs include only the umbrella <httpserver.hpp> (public headers
26+
# reject direct inclusion) and are compiled with -fsyntax-only, so no
27+
# build tree, config.h, or libmicrohttpd is needed — the script runs
28+
# standalone from a fresh checkout. -std=c++20 matches the umbrella's
29+
# own version gate (httpserver.hpp #errors below C++20) and the
30+
# CHECK_HEADERS_CXX invocation in Makefile.am.
31+
#
32+
# The compiler is ${CXX:-c++}; the Makefile target passes the configured
33+
# $(CXX) through.
34+
#
35+
# Exit codes:
36+
# 0 attribute in place (positive control compiles, negative TU fails
37+
# with a deprecation diagnostic)
38+
# 1 regression (deprecated overload compiled cleanly, positive
39+
# control broke, or the negative TU failed for the wrong reason)
40+
set -euo pipefail
41+
42+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
43+
# The configured $(CXX) may be multi-word (e.g. "g++ -std=c++20"), so
44+
# split it into an argv array rather than quoting it as one command name.
45+
read -r -a CXX_CMD <<< "${CXX:-c++}"
46+
COMMON_FLAGS=(-std=c++20 -fsyntax-only -I"$REPO_ROOT/src"
47+
-Werror=deprecated-declarations)
48+
49+
WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/check-deprecated-cookie.XXXXXX")"
50+
trap 'rm -rf "$WORKDIR"' EXIT
51+
52+
# Positive control: structured overload, must compile cleanly.
53+
cat > "$WORKDIR/positive.cpp" <<'EOF'
54+
#include <httpserver.hpp>
55+
void structured_overload_ok(httpserver::http_response& r) {
56+
r.with_cookie(httpserver::cookie{}.with_name("sid").with_value("v"));
57+
}
58+
EOF
59+
60+
# Negative TU: deprecated legacy overload, must trip
61+
# -Werror=deprecated-declarations.
62+
cat > "$WORKDIR/negative.cpp" <<'EOF'
63+
#include <httpserver.hpp>
64+
#include <string>
65+
void deprecated_overload_trips(httpserver::http_response& r) {
66+
r.with_cookie(std::string("sid"), std::string("v"));
67+
}
68+
EOF
69+
70+
echo "check-deprecated-cookie-overload: compiler: ${CXX_CMD[*]}"
71+
72+
if ! "${CXX_CMD[@]}" "${COMMON_FLAGS[@]}" "$WORKDIR/positive.cpp" \
73+
2>"$WORKDIR/positive.log"; then
74+
echo "check-deprecated-cookie-overload: FAIL — positive control did not compile" >&2
75+
echo " the structured with_cookie(cookie) TU must build cleanly under" >&2
76+
echo " -Werror=deprecated-declarations; fix the header (or this gate) first:" >&2
77+
sed 's/^/ /' "$WORKDIR/positive.log" >&2
78+
exit 1
79+
fi
80+
echo " positive control: structured with_cookie(cookie) compiles cleanly"
81+
82+
if "${CXX_CMD[@]}" "${COMMON_FLAGS[@]}" "$WORKDIR/negative.cpp" \
83+
2>"$WORKDIR/negative.log"; then
84+
echo "check-deprecated-cookie-overload: FAIL — with_cookie(std::string, std::string) compiled without a deprecation error" >&2
85+
echo " the [[deprecated]] attribute on the legacy overload in" >&2
86+
echo " src/httpserver/http_response.hpp has been removed or no longer fires" >&2
87+
exit 1
88+
fi
89+
90+
if ! grep -qi "deprecated" "$WORKDIR/negative.log"; then
91+
echo "check-deprecated-cookie-overload: FAIL — negative TU failed for the wrong reason" >&2
92+
echo " expected a -Wdeprecated-declarations diagnostic; got:" >&2
93+
sed 's/^/ /' "$WORKDIR/negative.log" >&2
94+
exit 1
95+
fi
96+
echo " negative check: legacy with_cookie(string, string) trips -Werror=deprecated-declarations"
97+
98+
echo "check-deprecated-cookie-overload: PASS — [[deprecated]] attribute is in place"

scripts/check-parallel-install-lane.sh

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -49,44 +49,25 @@ fi
4949
fail=0
5050

5151
# (a) valid YAML + (b) an include entry carrying `parallel-install: check`.
52-
# Both are structural, so PyYAML parses the file and inspects the matrix.
53-
if ! python3 - "$WF" <<'PY'
52+
# Both are structural: the shared locator (scripts/lib/find-matrix-includes.py)
53+
# parses the file with PyYAML and emits strategy.matrix.include as JSON; the
54+
# short per-gate check below inspects it.
55+
if ! include_json="$(python3 scripts/lib/find-matrix-includes.py "$WF")" \
56+
|| ! printf '%s' "$include_json" | python3 -c '
57+
import json
5458
import sys
55-
try:
56-
import yaml
57-
except ImportError:
58-
print(" PyYAML not importable — install with 'pip install pyyaml'", file=sys.stderr)
59-
sys.exit(10)
60-
61-
path = sys.argv[1]
62-
try:
63-
doc = yaml.safe_load(open(path))
64-
except Exception as e: # noqa: BLE001 - surface any parse failure
65-
print(f" (a) YAML parse error: {e}", file=sys.stderr)
66-
sys.exit(2)
67-
68-
# Locate the matrix include list in whichever job defines it (job name is
69-
# not hard-coded so the gate survives a rename).
70-
include = None
71-
for job_name, job in (doc.get("jobs") or {}).items():
72-
if not isinstance(job, dict):
73-
continue
74-
inc = (((job.get("strategy") or {}).get("matrix") or {}).get("include"))
75-
if isinstance(inc, list):
76-
include = inc
77-
break
78-
if include is None:
79-
print(" (b) could not locate strategy.matrix.include in any job", file=sys.stderr)
80-
sys.exit(3)
8159
60+
include = json.load(sys.stdin)
8261
opted_in = [e for e in include
8362
if isinstance(e, dict) and e.get("parallel-install") == "check"]
8463
if not opted_in:
85-
print(" (b) no matrix entry carries `parallel-install: check`", file=sys.stderr)
64+
print(" (b) no matrix entry carries `parallel-install: check`",
65+
file=sys.stderr)
8666
sys.exit(4)
8767
88-
print(f" (a) YAML parses; (b) {len(opted_in)} matrix entry(ies) carry parallel-install: check")
89-
PY
68+
print(f" (a) YAML parses; "
69+
f"(b) {len(opted_in)} matrix entry(ies) carry parallel-install: check")
70+
'
9071
then
9172
echo " (a)/(b) FAILED" >&2
9273
fail=1

scripts/check-valgrind-lane.sh

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,37 +52,16 @@ fi
5252

5353
fail=0
5454

55-
# (a) valid YAML + (b) valgrind include entry.
56-
# Both are structural, so PyYAML parses the file and inspects the matrix.
57-
if ! python3 - "$WF" <<'PY'
55+
# (a) valid YAML + (b) valgrind include entries.
56+
# Both are structural: the shared locator (scripts/lib/find-matrix-includes.py)
57+
# parses the file with PyYAML and emits strategy.matrix.include as JSON; the
58+
# short per-gate check below inspects it.
59+
if ! include_json="$(python3 scripts/lib/find-matrix-includes.py "$WF")" \
60+
|| ! printf '%s' "$include_json" | python3 -c '
61+
import json
5862
import sys
59-
try:
60-
import yaml
61-
except ImportError:
62-
print(" PyYAML not importable — install with 'pip install pyyaml'", file=sys.stderr)
63-
sys.exit(10)
64-
65-
path = sys.argv[1]
66-
try:
67-
doc = yaml.safe_load(open(path))
68-
except Exception as e: # noqa: BLE001 - surface any parse failure
69-
print(f" (a) YAML parse error: {e}", file=sys.stderr)
70-
sys.exit(2)
71-
72-
# Locate the matrix include list in whichever job defines it (job name is
73-
# not hard-coded so the gate survives a rename).
74-
include = None
75-
for job_name, job in (doc.get("jobs") or {}).items():
76-
if not isinstance(job, dict):
77-
continue
78-
inc = (((job.get("strategy") or {}).get("matrix") or {}).get("include"))
79-
if isinstance(inc, list):
80-
include = inc
81-
break
82-
if include is None:
83-
print(" (b) could not locate strategy.matrix.include in any job", file=sys.stderr)
84-
sys.exit(3)
8563
64+
include = json.load(sys.stdin)
8665
VALGRIND_TOOLS = {"valgrind-memcheck", "valgrind-helgrind", "valgrind-drd"}
8766
found = {e["build-type"] for e in include
8867
if isinstance(e, dict) and e.get("build-type") in VALGRIND_TOOLS}
@@ -91,8 +70,9 @@ if found != VALGRIND_TOOLS:
9170
print(f" (b) missing valgrind matrix entries: {missing}", file=sys.stderr)
9271
sys.exit(4)
9372
94-
print(" (a) YAML parses; (b) valgrind-memcheck + valgrind-helgrind + valgrind-drd entries present")
95-
PY
73+
print(" (a) YAML parses; (b) valgrind-memcheck + valgrind-helgrind"
74+
" + valgrind-drd entries present")
75+
'
9676
then
9777
echo " (a)/(b) FAILED" >&2
9878
fail=1
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
# find-matrix-includes.py — shared helper for the workflow lane gates
3+
# (check-valgrind-lane.sh, check-parallel-install-lane.sh, and any
4+
# future lane gate).
5+
#
6+
# Parses a GitHub Actions workflow file with PyYAML and prints its
7+
# strategy.matrix.include list to stdout as JSON. The job name is not
8+
# hard-coded so the gates survive a job rename; the first job that
9+
# defines an include list wins.
10+
#
11+
# Exit codes (mirrored by the calling gates' (a)/(b) assertions):
12+
# 0 include list printed as JSON on stdout
13+
# 2 YAML parse error
14+
# 3 no strategy.matrix.include found in any job
15+
# 10 PyYAML not importable
16+
import json
17+
import sys
18+
19+
try:
20+
import yaml
21+
except ImportError:
22+
print(" PyYAML not importable — install with 'pip install pyyaml'",
23+
file=sys.stderr)
24+
sys.exit(10)
25+
26+
path = sys.argv[1]
27+
try:
28+
doc = yaml.safe_load(open(path))
29+
except Exception as e: # noqa: BLE001 - surface any parse failure
30+
print(f" (a) YAML parse error: {e}", file=sys.stderr)
31+
sys.exit(2)
32+
33+
include = None
34+
for job_name, job in (doc.get("jobs") or {}).items():
35+
if not isinstance(job, dict):
36+
continue
37+
inc = (((job.get("strategy") or {}).get("matrix") or {}).get("include"))
38+
if isinstance(inc, list):
39+
include = inc
40+
break
41+
if include is None:
42+
print(" (b) could not locate strategy.matrix.include in any job",
43+
file=sys.stderr)
44+
sys.exit(3)
45+
46+
print(json.dumps(include))

specs/tasks/M7-v2-cleanup/TASK-067.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ Three transitional surfaces survive TASK-053's v1→v2 dispatch cutover:
1313
Cut them in a single, well-tested PR so the v2 dispatch path is the only one.
1414

1515
**Action Items:**
16-
- [ ] Identify every reader of `registered_resources*` (grep `src/`). For each: rewrite to consume the v2 route table or registration journal directly.
17-
- [ ] Delete `registered_resources*` maps + mutex from `webserver_impl`.
18-
- [ ] Delete `namespace compat` from `create_webserver.hpp`, the `[[deprecated]]` overload, and the paired `-Wdeprecated-declarations` push/pop.
19-
- [ ] Drop the same `-Wdeprecated-declarations` suppression in `src/http_resource.cpp:81-115` once the migration to `std::atomic<std::shared_ptr<T>>` (TASK-070) makes it unnecessary, or hand it off to TASK-070 explicitly if scope expands.
20-
- [ ] RELEASE_NOTES.md: document the binary/source incompatibility under "Removals".
16+
- [x] Identify every reader of `registered_resources*` (grep `src/`). For each: rewrite to consume the v2 route table or registration journal directly.
17+
- [x] Delete `registered_resources*` maps + mutex from `webserver_impl`.
18+
- [x] Delete `namespace compat` from `create_webserver.hpp`, the `[[deprecated]]` overload, and the paired `-Wdeprecated-declarations` push/pop.
19+
- [x] Drop the same `-Wdeprecated-declarations` suppression in `src/http_resource.cpp:81-115` once the migration to `std::atomic<std::shared_ptr<T>>` (TASK-070) makes it unnecessary, or hand it off to TASK-070 explicitly if scope expands. *(Handed off to TASK-070, which carries it in its action items; deliberately deferred, not forgotten.)*
20+
- [x] RELEASE_NOTES.md: document the binary/source incompatibility under "Removals".
2121

2222
**Dependencies:**
2323
- Blocked by: TASK-053 (Done)

specs/tasks/M7-v2-cleanup/TASK-080.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ noise rather than the threshold.
2323
**Acceptance Criteria:**
2424
- The latency gate is at 10× warmup median (or the documented tightest stable floor) with measurement data.
2525
- The test has not flaked in the last 50 CI runs across the matrix.
26+
*(DEFERRED at merge — unenforceable at PR-time; verified by post-merge
27+
monitoring of `feature/v2.0` CI. See `test/PERFORMANCE.md` §"Acceptance
28+
criterion verification — 50-run stability" for the proxy evidence and
29+
the Linux-sweep gap.)*
2630
- A `test/PERFORMANCE.md` entry records the gate, the runner profile, and the noise floor.
2731
- Typecheck passes.
2832
- Tests pass.

0 commit comments

Comments
 (0)