# NGINX Markdown for Agents - Top-level build/test entrypoints

UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)

LINUX_LIBC := $(shell if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then echo musl; elif command -v ldd >/dev/null 2>&1 && ldd /bin/sh 2>&1 | grep -qi musl; then echo musl; else echo gnu; fi)

ifeq ($(UNAME_S),Darwin)
  MACOS_MIN_VERSION ?= 11.0
  MACOSX_DEPLOYMENT_TARGET ?= $(MACOS_MIN_VERSION)
  export MACOSX_DEPLOYMENT_TARGET
endif

ifndef RUST_TARGET
  ifeq ($(UNAME_S),Darwin)
    ifeq ($(UNAME_M),arm64)
      RUST_TARGET = aarch64-apple-darwin
    else ifeq ($(UNAME_M),x86_64)
      RUST_TARGET = x86_64-apple-darwin
    else
      $(error Unsupported Darwin architecture: $(UNAME_M))
    endif
  else ifeq ($(UNAME_S),Linux)
    ifeq ($(UNAME_M),aarch64)
      ifeq ($(LINUX_LIBC),musl)
        RUST_TARGET = aarch64-unknown-linux-musl
      else
        RUST_TARGET = aarch64-unknown-linux-gnu
      endif
    else ifeq ($(UNAME_M),x86_64)
      ifeq ($(LINUX_LIBC),musl)
        RUST_TARGET = x86_64-unknown-linux-musl
      else
        RUST_TARGET = x86_64-unknown-linux-gnu
      endif
    else
      $(error Unsupported Linux architecture: $(UNAME_M))
    endif
  else
    $(error Unsupported OS/architecture: $(UNAME_S)/$(UNAME_M))
  endif
endif

RUST_DIR := components/rust-converter
NGINX_MODULE_DIR ?= components/nginx-module
NGINX_MODULE_DIR_CANONICAL := $(abspath $(NGINX_MODULE_DIR))
EXPECTED_NGINX_MODULE_DIR := $(abspath components/nginx-module)
ifneq ($(NGINX_MODULE_DIR_CANONICAL),$(EXPECTED_NGINX_MODULE_DIR))
$(error NGINX_MODULE_DIR must resolve to $(EXPECTED_NGINX_MODULE_DIR))
endif
NGINX_TEST_DIR := $(NGINX_MODULE_DIR)/tests
CORPUS_DIR := tests/corpus
RUST_LIB := $(RUST_DIR)/target/$(RUST_TARGET)/release/libnginx_markdown_converter.a
RUST_HEADER := $(RUST_DIR)/include/markdown_converter.h
NGINX_HEADER := $(NGINX_MODULE_DIR)/src/markdown_converter.h
MODULE_SO ?= build/ngx_http_markdown_filter_module.so
PREFIX ?= /usr
LIBDIR ?= $(PREFIX)/lib
DESTDIR ?=
STYLE_BASE ?= HEAD
SCHEMA_RELEASE_VERSION ?= 0.9.2
MODULE_INSTALL_DIR := $(LIBDIR)/nginx/modules
NGINX_MODULES_AVAILABLE_DIR := $(PREFIX)/share/nginx/modules-available
DOC_INSTALL_DIR := $(PREFIX)/share/doc/nginx-markdown-for-agents
LICENSE_INSTALL_DIR := $(PREFIX)/share/licenses/nginx-markdown-for-agents

.PHONY: all build rust-lib rust-lib-debug copy-headers check-headers capability-check \
        install \
        test test-rust rust-fmt-check rust-clippy-check test-rust-doc test-nginx-unit test-c-unit-gcc test-nginx-unit-streaming test-nginx-unit-clang-smoke test-nginx-unit-sanitize-smoke \
        test-nginx-integration test-e2e test-e2e-canonical test-e2e-rust test-e2e-contract-scripts test-streaming-conflict-pbt test-upgrade-rollback-contract test-all test-property test-rust-fuzz-smoke fuzz-smoke sonar-compile-db \
        test-all-e2e test-all-coverage \
        test-benchmark test-benchmark-compare test-benchmark-summary \
        test-corpus-determinism reason-codegen-generate reason-codegen-check \
        official-feature-manifest-generate \
        harness-check harness-check-full harness-security-checks test-harness workflow-context-check regex-security-check e2e-streaming-config-check sonar-encoding-check release-supply-chain-check public-surface-drift-check schema-drift-check kb-contract-check \
        security-static security-actionlint security-shellcheck security-gitleaks security-semgrep security-cargo-deny \
        supply-chain supply-chain-trivy supply-chain-sbom \
        complexity-check \
        docs-check docs-style-check docs-style-check-strict docs-style-check-regression docs-style-check-baseline decompression-metric-contract-check license-check release-notes release-gates-check release-gates-check-070 release-gates-check-070-docker release-gates-check-080 release-gates-check-080-regression release-gates-check-08x release-gates-check-092-canonical release-gates-check-092 release-gates-check-all release-gates-check-strict \
        release-pytest-check test-nginx-integration-c \
        release-matrix-check pre-lts-status-check \
        streaming-evidence-check \
        release-candidate-evidence-check artifact-registry-check release-evidence-manifest-check \
        test-rust-fuzz-qualification test-e2e-rust-soak \
        docs-check-base release-perf-evidence-blocking \
        perf-evidence-check \
        test-production-examples-nginx-t test-production-examples-e2e-smoke \
        verify-large-e2e verify-huge-native-e2e verify-huge-allowed-native-e2e \
        verify-chunked-native-e2e verify-chunked-native-e2e-smoke verify-chunked-native-e2e-stress \
        verify-brotli-streaming-e2e \
        verify-http2-alpn-e2e \
        verify-encoding-chain-e2e \
        verify-streaming-failure-cache-e2e \
        verify-streaming-failure-cache-e2e-plan \
        verify-metrics-endpoint-e2e verify-conditional-requests-e2e verify-config-merge-e2e \
        verify-auth-cache-e2e verify-status-codes-e2e \
        verify-subrequest-filter-ordering-native-e2e verify-non-streaming-module-e2e \
        verify-diagnostics-access-phase-e2e \
        test-rust-streaming \
        coverage-c coverage-rust coverage-sonar-xml coverage-all coverage-gate \
        clean help

all: build

build: rust-lib copy-headers capability-check
	@echo "Build complete for $(RUST_TARGET)"
	@echo "Rust library: $(RUST_LIB)"

RUST_RELEASE_FEATURES ?= streaming

rust-lib:
	@echo "Building Rust library for $(RUST_TARGET)..."
	cd $(RUST_DIR) && cargo build --locked --target $(RUST_TARGET) --release --features $(RUST_RELEASE_FEATURES)
	@command -v cbindgen >/dev/null 2>&1 && [ "$$(cbindgen --version)" = "cbindgen 0.29.4" ] || { \
	  echo "ERROR: cbindgen 0.29.4 required (found: $$(cbindgen --version 2>/dev/null || echo none)). The generated header is committed and fingerprinted; versions differ and produce a different header, which fails the ABI drift check. Install with: cargo install cbindgen --version 0.29.4 --locked" >&2; \
	  exit 127; \
	}
	@echo "Generating C header with cbindgen..."
	cd $(RUST_DIR) && mkdir -p include && cbindgen --quiet --config cbindgen.toml --crate nginx-markdown-converter --output include/markdown_converter.h
	python3 tools/harness/normalize_cbindgen_header.py

rust-lib-debug:
	@echo "Building Rust library (debug) for $(RUST_TARGET)..."
	cd $(RUST_DIR) && cargo build --locked --target $(RUST_TARGET) --features $(RUST_RELEASE_FEATURES)

copy-headers:
	@echo "Copying headers to nginx module source..."
	cp $(RUST_HEADER) $(NGINX_HEADER)

check-headers:
	@cmp -s $(RUST_HEADER) $(NGINX_HEADER) && echo "Headers are in sync" || (echo "Header mismatch: run 'make copy-headers'" && exit 1)

capability-check:
	@python3 tools/release/gates/verify_build_capabilities.py --source-root . --features "$(RUST_RELEASE_FEATURES)"

install:
	@test -f "$(MODULE_SO)" || { echo "FAIL: $(MODULE_SO) not found" >&2; exit 1; }
	install -d "$(DESTDIR)$(MODULE_INSTALL_DIR)"
	install -d "$(DESTDIR)$(NGINX_MODULES_AVAILABLE_DIR)"
	install -d "$(DESTDIR)$(DOC_INSTALL_DIR)"
	install -d "$(DESTDIR)$(LICENSE_INSTALL_DIR)"
	install -m 0644 "$(MODULE_SO)" "$(DESTDIR)$(MODULE_INSTALL_DIR)/ngx_http_markdown_filter_module.so"
	install -m 0644 packaging/nfpm/modules-available/mod-markdown.conf "$(DESTDIR)$(NGINX_MODULES_AVAILABLE_DIR)/mod-markdown.conf"
	install -m 0644 README.md "$(DESTDIR)$(DOC_INSTALL_DIR)/README.md"
	install -m 0644 docs/guides/INSTALL.md "$(DESTDIR)$(DOC_INSTALL_DIR)/INSTALL.md"
	install -m 0644 docs/guides/PACKAGE_INSTALLATION.md "$(DESTDIR)$(DOC_INSTALL_DIR)/PACKAGE_INSTALLATION.md"
	install -m 0644 docs/guides/PACKAGE_COMPATIBILITY.md "$(DESTDIR)$(DOC_INSTALL_DIR)/PACKAGE_COMPATIBILITY.md"
	install -m 0644 LICENSE "$(DESTDIR)$(LICENSE_INSTALL_DIR)/LICENSE"

# Default smoke test
# keeps feedback fast for local development
test: build
	@$(MAKE) -C $(NGINX_TEST_DIR) unit-smoke

rust-fmt-check:
	@echo "=== Rust Formatting Check ==="
	cd $(RUST_DIR) && cargo fmt --all -- --check
	cargo fmt --manifest-path tools/corpus/test-corpus-conversion/Cargo.toml --all -- --check
	@echo "  Rust Formatting Check: PASSED"

rust-clippy-check:
	@echo "=== Rust Clippy Check ==="
	cargo clippy --manifest-path $(RUST_DIR)/Cargo.toml --all-targets --all-features -- -D warnings
	cargo clippy --manifest-path tools/corpus/test-corpus-conversion/Cargo.toml --all-targets -- -D warnings
	@echo "  Rust Clippy Check: PASSED"

test-rust: rust-fmt-check
	cd $(RUST_DIR) && cargo build --locked --release --example perf_baseline
	cd $(RUST_DIR) && cargo test --locked --release --all --all-features
	cd $(RUST_DIR) && cargo test --locked --release --doc --all-features

test-rust-streaming:
	cd $(RUST_DIR) && cargo test --locked --features streaming

test-rust-doc:
	@echo "Running Rust doctests (all features)..."
	cd $(RUST_DIR) && cargo test --locked --doc --all-features

test-rust-fuzz-smoke:
	cd $(RUST_DIR) && cargo +nightly fuzz run parser_html -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run ffi_convert -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run security_validator -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_streaming_no_panic -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_streaming_chunk_split -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_streaming_malformed -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_decompression -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_url_validation -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run convert_html -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run streaming_chunks -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run negotiation_and_headers -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_encoding_chain -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_multilayer_decode -- -max_total_time=5
	cd $(RUST_DIR) && cargo +nightly fuzz run fuzz_trusted_proxy_cidr -- -max_total_time=5

fuzz-smoke:
	cd $(RUST_DIR) && cargo +nightly fuzz run convert_html -- -max_total_time=30
	cd $(RUST_DIR) && cargo +nightly fuzz run streaming_chunks -- -max_total_time=30
	cd $(RUST_DIR) && cargo +nightly fuzz run negotiation_and_headers -- -max_total_time=30

test-nginx-unit:
	$(MAKE) -C $(NGINX_TEST_DIR) unit

# Run the NGINX C unit suite under real GCC (Ubuntu) inside Docker.  Local
# `gcc` on macOS is an Apple clang alias, which masks two CI-only failure
# classes: GCC -Werror hard errors on incompatible-pointer-types, and
# uninitialized stack structs that crash only at GCC -O0 (clang/O2 pass by
# chance).  Run this before pushing any C test/module change; the container
# output dir must be cleaned afterwards because Linux ELF binaries cannot
# execute on macOS (make clean in $(NGINX_TEST_DIR) reuses the shared build/).
test-c-unit-gcc:
	@command -v docker >/dev/null 2>&1 || { echo "FAIL: docker is required for test-c-unit-gcc" >&2; exit 1; }
	@echo "=== C unit suite under Ubuntu GCC (Docker) ==="
	@docker_rc=0; \
	docker run --rm -e HOST_UID="$(shell id -u)" -e HOST_GID="$(shell id -g)" \
	    -v "$(CURDIR)":/repo -w /repo/components/nginx-module/tests \
	    ubuntu:24.04@sha256:019e8eb29a85e74d64925745884f2ec79aa27e3feab36353d24656f4d6b89467 bash -c 'set -e; trap "chown -R $$HOST_UID:$$HOST_GID /repo/components/nginx-module/tests/build 2>/dev/null || true" EXIT; apt-get update -qq && apt-get install -y -qq gcc make libz-dev libbrotli-dev python3 valgrind && make clean && make unit' || docker_rc=$$?; \
	echo "=== Cleaning container artifacts from local build dir ==="; \
	cleanup_rc=0; \
	$(MAKE) -C $(NGINX_TEST_DIR) clean || cleanup_rc=$$?; \
	if [ "$$docker_rc" -ne 0 ]; then \
		echo "FAIL: C unit suite under Ubuntu GCC (exit $$docker_rc)" >&2; \
		exit "$$docker_rc"; \
	fi; \
	if [ "$$cleanup_rc" -ne 0 ]; then \
		echo "FAIL: container artifact cleanup (exit $$cleanup_rc)" >&2; \
		exit "$$cleanup_rc"; \
	fi; \
	echo "Clean OK"; \
	echo "OK: C unit suite passed under Ubuntu GCC"

test-nginx-unit-streaming:
	$(MAKE) -C $(NGINX_TEST_DIR) unit-streaming

test-nginx-unit-clang-smoke:
	$(MAKE) -C $(NGINX_TEST_DIR) unit-clang-smoke

test-nginx-unit-sanitize-smoke:
	$(MAKE) -C $(NGINX_TEST_DIR) unit-sanitize-smoke

# Package compatibility regression suites. Standalone entry point for the
# release workflow's package-gate step; runs the three maintainer-script
# and lifecycle suites that protect published DEB/RPM artifacts.
.PHONY: test-package-compatibility
test-package-compatibility:
	@echo "=== Package Compatibility Suites ==="
	@for t in test-preinstall-version-policy.sh test-package-removal-guard.sh test-maintainer-script-executable-trust.sh test-deb-upgrade-invariant.sh; do \
	  echo "  [$$t]"; \
	  bash packaging/tests/$$t || { echo "FAIL: $$t" >&2; exit 1; }; \
	done
	@echo "=== Package Compatibility Suites: ALL PASSED ==="

test-nginx-integration:
	$(MAKE) -C $(NGINX_TEST_DIR) integration-c
	$(MAKE) -C $(NGINX_TEST_DIR) integration-nginx

# Pure-C integration harness (no nginx binary needed) — mirrors the CI
# `integration-c` step exactly; catches C-level runtime regressions locally.
test-nginx-integration-c:
	$(MAKE) -C $(NGINX_TEST_DIR) integration-c

test-e2e:
	$(MAKE) -C $(NGINX_TEST_DIR) e2e

# Canonical shell qualification suite, including strict filter-ordering and
# auth-subrequest checks when a running fixture is supplied.
test-e2e-canonical:
	bash tools/e2e/run_e2e_suite.sh

E2E_HARNESS_DIR := tools/e2e-harness

test-e2e-rust:
	@echo "Building e2e-harness..."
	cd $(E2E_HARNESS_DIR) && cargo build --locked
	@echo "Running e2e-harness migrated scenarios..."
	cd $(E2E_HARNESS_DIR) && cargo run --locked -- suite --profile smoke

test-e2e-contract-scripts:
	@case "$${NGINX_BIN:-}" in \
		'') NGINX_BIN="$$(command -v nginx 2>/dev/null || true)" ;; \
		*) ;; \
	esac; \
	if test -z "$${NGINX_BIN:-}"; then echo "ERROR: set NGINX_BIN or install a module-enabled nginx" >&2; exit 2; fi; \
	set -e; \
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_error_policy_values.sh; \
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_removed_directives.sh; \
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_removed_directives_pbt.sh; \
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_streaming_conflict_pbt.sh; \
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_upgrade_rollback_contract.sh; \
	if test -n "$${NGINX_URL:-}"; then \
		REQUIRE_FILTER_ORDERING_ALL="$${REQUIRE_FILTER_ORDERING_ALL:-$${RELEASE_GATE_REQUIRE_FILTER_ORDERING:-0}}" \
			bash tests/e2e/filter_ordering_test.sh; \
		REQUIRE_AUTH_SUBREQUEST=1 bash tests/e2e/subrequest_ssi_test.sh; \
	elif test "$${RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E:-0}" = "1"; then \
		echo "SKIP: filter ordering / subrequest_ssi require NGINX_URL and a running fixture (RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1)" >&2; \
	else \
		echo "FAIL: filter ordering / subrequest_ssi are part of final E2E qualification and must not be skipped; provide NGINX_URL pointing at a module-enabled fixture (set RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 only for non-release local runs)" >&2; \
		exit 1; \
	fi

test-streaming-conflict-pbt:
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_streaming_conflict_pbt.sh $(E2E_ARGS)

test-upgrade-rollback-contract:
	NGINX_BIN="$${NGINX_BIN}" bash tests/compatibility/test_upgrade_rollback_contract.sh

# ---------------------------------------------------------------------------
# test-all: aggregate every CI-checkable gate that can run on the current
# host.  Mirrors the blocking jobs in .github/workflows/ci.yml:
#   docs-check / harness-tooling / rust-quality / nginx-c-tests /
#   release-092-contract-gates / matrix-release-tests
# Native-E2E jobs (runtime-regressions, brotli-build-matrix) and the
# coverage gate need a module-enabled NGINX binary and are aggregated
# separately: `make test-all-e2e NGINX_BIN=...` and
# `make test-all-coverage NGINX_BIN=...` (see below).
# ---------------------------------------------------------------------------
TEST_ALL_CORE := \
	build \
	check-headers \
	rust-fmt-check \
	rust-clippy-check \
	test-rust \
	test-nginx-unit \
	test-nginx-unit-streaming \
	test-nginx-unit-clang-smoke \
	test-nginx-unit-sanitize-smoke \
	test-nginx-integration-c \
	test-property \
	docs-check \
	kb-contract-check \
	regex-security-check \
	e2e-streaming-config-check \
	sonar-encoding-check \
	harness-check \
	harness-security-checks \
	test-harness \
	public-surface-drift-check \
	schema-drift-check \
	reason-codegen-check \
	release-gates-check \
	release-gates-check-070-strict \
	release-pytest-check \
        release-matrix-check \
        capability-check \
	test-corpus-determinism \
	complexity-check \
	workflow-context-check \
	license-check

test-all:
	@echo "=== test-all: running all CI-mirrored gates ==="
	@$(MAKE) $(TEST_ALL_CORE)
	@echo
	@echo "=================================================="
	@echo " test-all: ALL GATES PASSED"
	@echo "=================================================="
	@echo
	@echo "E2E/coverage gates need a module-enabled NGINX:"
	@echo "  make test-all-e2e NGINX_BIN=/path/to/nginx"
	@echo "  make test-all-coverage NGINX_BIN=/path/to/nginx"

# Native E2E suite — the runtime-regressions + brotli-build-matrix jobs
# from ci.yml.  Requires NGINX_BIN (a module-enabled binary) or an
# NGINX_URL fixture; fails with guidance when neither is provided.
#
# Two targets manage their own NGINX lifecycle (real-NGINX IMS,
# filter-ordering qualification) and cannot
# attach to an external NGINX_URL fixture, so they are skipped with an
# explicit SKIP=1 when only NGINX_URL is supplied.  NGINX_BIN runs keep
# every scenario.
test-all-e2e:
	@test -n "$(NGINX_BIN)" -o -n "$(NGINX_URL)" || { \
		echo "FAIL: test-all-e2e requires NGINX_BIN (module-enabled nginx) or NGINX_URL (running fixture)" >&2; \
		echo "  A module-enabled nginx binary is required; sync one from the verification host and use:" >&2; \
		echo "    make test-all-e2e NGINX_BIN=/tmp/nginx-ims-verify.XXXX/objs/nginx" >&2; \
		exit 2; \
	}
	@echo "=== test-all-e2e: native E2E suite ==="
	$(MAKE) verify-conditional-requests-e2e
	$(MAKE) verify-metrics-endpoint-e2e
	$(MAKE) verify-auth-cache-e2e
	$(MAKE) verify-status-codes-e2e
	$(MAKE) verify-chunked-native-e2e-smoke
	$(MAKE) verify-large-e2e
	$(MAKE) verify-brotli-streaming-e2e
	$(MAKE) verify-http2-alpn-e2e
	@test -n "$(NGINX_BIN)" || { \
		echo "SKIP: real-NGINX IMS / filter-ordering manage their own NGINX (NGINX_BIN-only; skipped in NGINX_URL fixture mode)" >&2; \
		true; \
	}
	@if test -n "$(NGINX_BIN)"; then \
		set -e; \
		$(MAKE) verify-real-nginx-ims-e2e; \
		$(MAKE) verify-subrequest-filter-ordering-native-e2e; \
	fi
	$(MAKE) verify-non-streaming-module-e2e
	@echo "=== test-all-e2e: ALL E2E SCENARIOS PASSED ==="

# Coverage gate — coverage-gate from ci.yml.  Requires lcov and a
# module-enabled NGINX binary for the C coverage collection.
test-all-coverage:
	@command -v lcov >/dev/null 2>&1 || { echo "FAIL: lcov is required for test-all-coverage" >&2; exit 127; }
	@test -n "$(NGINX_BIN)" || { \
		echo "FAIL: test-all-coverage requires NGINX_BIN so collect_nginx_coverage.sh can exercise the module (set RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 is NOT honored here)" >&2; \
		exit 2; \
	}
	$(MAKE) coverage-gate
	@echo "=== test-all-coverage: COVERAGE GATE PASSED ==="

# Property-based test suites: Rust proptest + Python Hypothesis + shell.
# Runs the proptest integration targets (tests/property_*.rs), the Python
# Hypothesis property tests (tests/property/*.py), and the shell property
# tests (tests/property/*.sh).
test-property:
	@echo "=== Property-based tests (Rust proptest) ==="
	@cd $(RUST_DIR) && for t in tests/property_*.rs; do \
		target="$$(basename "$$t" .rs)"; \
		echo "  running $$target"; \
		cargo test --locked --test "$$target" || exit 1; \
	done
	@echo "=== Property-based tests (Python Hypothesis) ==="
	@python3 -m pytest tests/property/ -q
	@echo "=== Property-based tests (shell) ==="
	@bash tests/property/test_log_prefix_canonical.sh
	@bash tests/property/test_log_prefix_preservation.sh

sonar-compile-db:
	./tools/sonar/generate_compile_commands.sh

# Corpus benchmark targets
CORPUS_CONVERTER_BIN := tools/corpus/test-corpus-conversion/target/release/test-corpus-conversion
CORPUS_REPORT := perf/reports/corpus-report.json
CORPUS_BASELINE := perf/baselines/corpus-baseline.json
CORPUS_VERDICT := perf/reports/corpus-verdict.json

test-benchmark:
	@echo "Generating large corpus fixtures..."
	tests/corpus/large/generate-large-fixtures.sh
	@echo "Validating corpus metadata..."
	tools/corpus/validate_corpus.sh
	@echo "Building test-corpus-conversion binary..."
	cd tools/corpus/test-corpus-conversion && cargo build --locked --release --quiet
	@echo "Running corpus benchmark..."
	python3 tools/perf/run_corpus_benchmark.py \
	--corpus-dir $(CORPUS_DIR) \
	--converter-bin $(CORPUS_CONVERTER_BIN) \
	--output $(CORPUS_REPORT) \
	--examples-dir perf/reports/examples

test-benchmark-compare:
	python3 tools/perf/compare_reports.py \
	--baseline $(CORPUS_BASELINE) \
	--current $(CORPUS_REPORT) \
	--thresholds perf/quality-thresholds.json \
	--output $(CORPUS_VERDICT)

test-benchmark-summary:
	python3 tools/perf/format_pr_summary.py \
	--report $(CORPUS_REPORT)

# Determinism corpus: every corpus fixture
# must produce byte-identical output across repeated independent converter
# runs.
test-corpus-determinism:
	bash tools/corpus/verify_determinism.sh

docs-check-base:
	python3 tools/docs/check_docs.py
	python3 tools/docs/check_packaging_docs.py
	python3 tools/docs/check_packaging_consistency.py
	$(MAKE) decompression-metric-contract-check
	python3 tools/docs/validate_packaging_matrix.py
	python3 tools/release/matrix/generate_release_contract_matrix.py --check
	python3 tools/render_release_matrix_docs.py --check
	python3 tools/release/matrix/validate_workflow_matrix_consumers.py
	python3 tools/release/gates/validate_release_matrix_schema.py
	$(MAKE) pre-lts-status-check

docs-check: docs-check-base
	python3 tools/harness/check_harness_sync.py
	PYTHONPATH=. python3 tools/harness/detect_doc_sync.py
	$(MAKE) kb-contract-check
	$(MAKE) docs-style-check-regression

# STE-inspired writing-style gates (non-native-reader friendly, see
# docs/WRITING_GUIDE.md and harness Rule 63).
# Routine docs-check keeps the changed-file regression gate; the repository-wide
# style budget is reserved for full Harness and release validation.
# docs-style-check: advisory scan, never blocks.
# docs-style-check-regression: files changed since STYLE_BASE (working tree +
# staged) must have zero warnings. Local invocations default to HEAD; CI must
# provide the actual fetched comparison base.
# docs-style-check-baseline: total warnings must not exceed the retained
# budget (0, see DEFAULT_BASELINE in check_writing_style.py); the maintained
# docs now pass the audit clean, so any warning fails this gate.
docs-style-check:
	python3 tools/docs/check_writing_style.py

docs-style-check-strict:
	python3 tools/docs/check_writing_style.py --strict

docs-style-check-regression:
	@test -n "$(STYLE_BASE)" || { echo "FAIL: STYLE_BASE is required for docs-style-check-regression" >&2; exit 1; }
	python3 tools/docs/check_writing_style.py --changed --base "$(STYLE_BASE)"

docs-style-check-baseline:
	python3 tools/docs/check_writing_style.py --baseline

kb-contract-check:
	python3 tools/docs/check_kb_contract.py

decompression-metric-contract-check:
	python3 tools/docs/check_decompression_metric_labels.py

release-notes:
	python3 tools/render_release_matrix_docs.py --release-notes

harness-check:
	python3 tools/harness/check_harness_sync.py
	python3 tools/harness/detect_public_surface_drift.py

public-surface-drift-check:
	python3 tools/harness/detect_public_surface_drift.py
	python3 tools/release/gates/compute_abi_fingerprints.py

schema-drift-check:
	python3 tools/release/gates/generate_schema_artifacts.py --check --version "$(SCHEMA_RELEASE_VERSION)"
	python3 tools/release/gates/validate_schema_drift.py --version "$(SCHEMA_RELEASE_VERSION)"

harness-check-full:
	$(MAKE) docs-check-base
	$(MAKE) docs-style-check-baseline
	python3 tools/harness/check_harness_sync.py --full
	$(MAKE) release-gates-check
	$(MAKE) harness-security-checks
	$(MAKE) test-harness
	$(MAKE) check-headers
	$(MAKE) complexity-check

regex-security-check:
	@echo "=== Regex/ReDoS Safety Check ==="
	python3 tools/harness/detect_regex_safety.py --strict
	python3 -m pytest tools/harness/tests/test_detect_regex_safety.py -q --tb=short
	bash tools/harness/tests/test_detect_regex_safety.sh
	@echo "  Regex/ReDoS Safety Check: PASSED"

sonar-encoding-check:
	@echo "=== SonarCloud Source Encoding Check ==="
	python3 tools/sonar/check_source_encoding.py --include-exceptions
	python3 -m pytest tools/sonar/tests/test_check_source_encoding.py -q --tb=short
	@echo "  SonarCloud Source Encoding Check: PASSED"

e2e-streaming-config-check:
	@echo "=== E2E Streaming Config Consistency Check (Rule 60) ==="
	python3 tools/harness/detect_e2e_streaming_config.py --strict
	python3 -m pytest tools/harness/tests/test_detect_e2e_streaming_config.py -q --tb=short
	bash tools/harness/tests/test_detect_e2e_streaming_config.sh
	@echo "  E2E Streaming Config Check: PASSED"

reason-codegen-generate:
	@echo "=== Generate Reason Registry Artifacts ==="
	python3 tools/reason-codegen/generate.py
	@echo "  Reason Registry Artifact Generation: PASSED"

reason-codegen-check:
	@echo "=== Reason Registry Code Generation Drift Check ==="
	python3 tools/reason-codegen/generate.py --check
	@echo "  Reason Code Generation Drift Check: PASSED"

official-feature-manifest-generate:
	@echo "=== Generate Official Build Feature Manifest ==="
	PYTHONPATH=. python3 tools/release/gates/validate_official_feature_manifest.py --write
	@echo "  Official Build Feature Manifest Generation: PASSED"

harness-security-checks:
	python3 tools/ci/validate_required_workflow_contexts.py
	python3 tools/harness/check_removed_directive_registry.py
	bash tools/harness/detect_cwe190_casts.sh
	PYTHONPATH=. python3 tools/harness/detect_cwe22_paths.py tools/ --strict
	bash tools/harness/detect_ffi_fat_pointer_transfer.sh
	bash tools/harness/detect_shell_hygiene.sh tools/
	PYTHONPATH=. python3 tools/harness/detect_const_correctness.py components/nginx-module/src
	bash tools/harness/detect_ci_supply_chain.sh
	PYTHONPATH=. python3 tools/harness/detect_release_supply_chain.py
	PYTHONPATH=. python3 tools/harness/detect_workflow_secret_scope.py
	PYTHONPATH=. python3 tools/harness/detect_production_auth_transport.py
	bash tools/harness/detect_header_hash_filter.sh
	bash tools/harness/detect_finalize_return.sh
	bash tools/harness/detect_ffi_struct_init.sh
	bash tools/harness/detect_c_pure_logic.sh
	bash tools/harness/detect_volatile_atomic.sh
	bash tools/harness/detect_nosonar_discipline.sh
	bash tools/harness/detect_ngx_log_arg_count.sh
	bash tools/harness/detect_pool_free.sh
	bash tools/harness/detect_ffi_panic_safety.sh --strict
	PYTHONPATH=. python3 tools/harness/detect_forward_decl_order.py components/nginx-module/src --strict
	PYTHONPATH=. python3 tools/harness/detect_duplicate_code.py components/nginx-module/src --strict
	PYTHONPATH=. python3 tools/harness/detect_orphan_comment_close.py
	PYTHONPATH=. python3 tools/harness/detect_ffi_dead_exports.py --check
	bash tools/harness/detect_ifdef_guard_visibility.sh
	bash tools/harness/detect_workflow_input_injection.sh
	bash tools/harness/detect_hardcoded_http_status.sh
	PYTHONPATH=. python3 tools/harness/detect_open_without_path_validation.py --path tools/ --strict
	python3 tools/harness/detect_e2e_streaming_config.py --strict
	python3 tools/harness/detect_regex_safety.py --strict
	PYTHONPATH=. python3 tools/harness/detect_python_complexity.py
	PYTHONPATH=. python3 tools/harness/detect_auto_generated_naming.py --strict
	bash tools/harness/detect_version_consistency.sh
	bash tools/harness/detect_backpressure_resume.sh
	bash tools/harness/detect_ngx_again_call_sites.sh
	bash tools/harness/detect_uninitialized_stack_struct.sh
	bash tools/harness/detect_decompression_budget.sh
	PYTHONPATH=. python3 tools/harness/detect_test_assertion_coverage.py
	PYTHONPATH=. python3 tools/harness/detect_html_sanitizer_invariants.py
	PYTHONPATH=. python3 tools/harness/detect_metrics_event_conservation.py
	PYTHONPATH=. python3 tools/harness/detect_access_before_method.py --strict
	PYTHONPATH=. python3 tools/harness/detect_elts_null_guard.py
	PYTHONPATH=. python3 tools/harness/detect_doc_sync.py
	python3 tools/harness/detect_representation_metadata_clearing.py --strict
	python3 tools/harness/detect_baseline_hand_edit.py
	python3 tools/harness/detect_scratch_files.py
	PYTHONPATH=. python3 tools/harness/detect_workflow_env_liveness.py

release-supply-chain-check:
	@echo "=== Release Supply-Chain Contracts ==="
	PYTHONPATH=. python3 tools/harness/detect_release_supply_chain.py
	PYTHONPATH=. python3 tools/harness/detect_workflow_secret_scope.py
	PYTHONPATH=. python3 tools/harness/detect_production_auth_transport.py
	python3 -m pytest \
		tools/harness/tests/test_security_boundary_detectors.py \
		tools/harness/tests/test_install_verified_rustup.py \
		-q --tb=short
	@echo "  Release Supply-Chain Contracts: PASSED"

complexity-check:
	@echo "=== Complexity Check ==="
	bash tools/complexity/check_complexity.sh
	PYTHONPATH=. python3 tools/harness/detect_python_complexity.py

test-harness:
	@echo "=== Harness Detector Unit Tests ==="
	PYTHONPATH=tools/ci python3 -m pytest tools/ci/test_validate_required_workflow_contexts.py -q --tb=short
	bash tools/harness/tests/test_detect_ffi_struct_init.sh
	bash tools/harness/tests/test_detect_c_pure_logic.sh
	bash tools/harness/tests/test_detect_volatile_atomic.sh
	bash tools/harness/tests/test_detect_cwe190_casts.sh
	bash tools/harness/tests/test_detect_nosonar_discipline.sh
	bash tools/harness/tests/test_detect_ngx_log_arg_count.sh
	bash tools/harness/tests/test_detect_pool_free.sh
	bash tools/harness/tests/test_detect_ffi_panic_safety.sh
	bash tools/harness/tests/test_detect_ffi_fat_pointer_transfer.sh
	bash tools/harness/tests/test_security_gitleaks_scope.sh
	bash tools/harness/tests/test_install_config_search.sh
	bash tools/harness/tests/test_detect_orphan_comment_close.sh
	bash tools/harness/tests/test_detect_ifdef_guard_visibility.sh
	bash tools/harness/tests/test_detect_workflow_input_injection.sh
	bash tools/harness/tests/test_detect_hardcoded_http_status.sh
	bash tools/harness/tests/test_detect_e2e_streaming_config.sh
	bash tools/harness/tests/test_filter_ordering_strict_mode.sh
	bash tools/harness/tests/test_detect_regex_safety.sh
	bash tools/harness/tests/test_detect_ngx_again_call_sites.sh
	bash tools/harness/tests/test_detect_uninitialized_stack_struct.sh
	bash tools/harness/tests/test_detect_decompression_budget.sh
	bash tools/harness/tests/test_detect_shell_hygiene.sh
	bash tools/harness/tests/test_check_postinst_safety.sh
	python3 -m pytest tools/harness/tests/ -q --tb=short -k "not check_harness_sync"

workflow-context-check:
	python3 tools/ci/validate_required_workflow_contexts.py

license-check:
	python3 tools/ci/check_c_licenses.py
	python3 tools/ci/check_rust_licenses.py
	python3 -m unittest tools/ci/test_check_third_party_notices.py
	python3 tools/ci/check_third_party_notices.py

security-static: security-actionlint security-shellcheck security-gitleaks security-semgrep security-cargo-deny

security-actionlint:
	@command -v actionlint >/dev/null 2>&1 || { echo "ERROR: actionlint not found. Install with: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12" >&2; exit 127; }
	@workflow_files=$$(find .github/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) | sort); \
	if [ -z "$$workflow_files" ]; then \
		echo "ERROR: no workflow files found under .github/workflows" >&2; \
		exit 1; \
	fi; \
	actionlint -color -shellcheck= $$workflow_files

security-shellcheck:
	@command -v shellcheck >/dev/null 2>&1 || { echo "ERROR: shellcheck not found. Install from https://www.shellcheck.net/ or your package manager." >&2; exit 127; }
	@tmp_files=$$(mktemp); \
	existing_files=$$(mktemp); \
	trap 'rm -f "$$tmp_files" "$$existing_files"' EXIT; \
	git ls-files -z -- ":(glob)*.sh" ":(glob)tools/**/*.sh" ":(glob)packaging/**/*.sh" ":(glob).clusterfuzzlite/*.sh" ":(glob)examples/**/*.sh" > "$$tmp_files"; \
	while IFS= read -r -d '' shell_file; do \
		if [ -f "$$shell_file" ]; then \
			printf '%s\0' "$$shell_file" >> "$$existing_files"; \
		fi; \
	done < "$$tmp_files"; \
	if [ ! -s "$$existing_files" ]; then \
		echo "No tracked shell scripts matched the security-static scope."; \
	else \
		xargs -0 shellcheck --severity=error -x -P tools/e2e -P tools/lib < "$$existing_files"; \
	fi

security-gitleaks:
	@command -v gitleaks >/dev/null 2>&1 || { echo "ERROR: gitleaks not found. Install with: go install github.com/zricethezav/gitleaks/v8@83d9cd684c87d95d656c1458ef04895a7f1cbd8e # v8.30.1" >&2; exit 127; }
	bash tools/security/run_gitleaks_tracked.sh

security-semgrep:
	@command -v semgrep >/dev/null 2>&1 || { echo "ERROR: semgrep not found. Install with: python3 -m pip install --user semgrep==1.166.0" >&2; exit 127; }
	semgrep --config .semgrep.yml --error --metrics=off

security-cargo-deny:
	@command -v cargo-deny >/dev/null 2>&1 || { echo "ERROR: cargo-deny not found. Install with: cargo install cargo-deny --version 0.19.8 --locked" >&2; exit 127; }
	@for manifest in components/rust-converter/Cargo.toml components/rust-converter/fuzz/Cargo.toml tools/corpus/test-corpus-conversion/Cargo.toml tools/e2e-harness/Cargo.toml; do \
		cargo deny --manifest-path "$$manifest" check --config deny.toml advisories licenses bans sources || exit $$?; \
	done

supply-chain: supply-chain-trivy supply-chain-sbom

TRIVY_LOCAL_SKIP_DIRS := \
	--skip-dirs .arts \
	--skip-dirs .codeartsdoer \
	--skip-dirs .kiro \
	--skip-dirs .serena \
	--skip-dirs .vscode \
	--skip-dirs .idea \
	--skip-dirs .fleet \
	--skip-dirs .codex-venv \
	--skip-dirs .trae \
	--skip-dirs docs/archive \
	--skip-dirs build \
	--skip-dirs dist \
	--skip-dirs out \
	--skip-dirs tmp \
	--skip-dirs temp \
	--skip-dirs .test-tmp \
	--skip-dirs coverage \
	--skip-dirs reports \
	--skip-dirs test-output \
	--skip-dirs test-results \
	--skip-dirs '**/target' \
	--skip-dirs '**/node_modules' \
	--skip-dirs '**/__pycache__'

supply-chain-trivy:
	@command -v trivy >/dev/null 2>&1 || { echo "ERROR: trivy not found. Install from https://aquasecurity.github.io/trivy/latest/getting-started/installation/." >&2; exit 127; }
	trivy fs --scanners vuln,misconfig,secret --ignore-unfixed \
		--severity HIGH,CRITICAL $(TRIVY_LOCAL_SKIP_DIRS) .

supply-chain-sbom:
	@command -v syft >/dev/null 2>&1 || { echo "ERROR: syft not found. Install from https://github.com/anchore/syft#installation." >&2; exit 127; }
	mkdir -p build/reports
	syft dir:. -o spdx-json=build/reports/sbom.spdx.json

# Current release-contract baseline.  Historical 0.5.0 document validators
# were retired; current observability, naming, package, and checksum contracts
# are validated through their feature-oriented gates.
release-gates-check:
	python3 tools/release/gates/validate_naming.py
	python3 tools/release/gates/validate_schema_drift.py --version "$(SCHEMA_RELEASE_VERSION)"
	$(MAKE) reason-codegen-check
	python3 tools/release/gates/validate_package_metadata.py
	python3 packaging/scripts/test_generate_checksums.py

# Lightweight local mirror of the CI/tag 070 strict gate (file-assertion only).
# Catches doc/workflow drift locally before CI does.
release-gates-check-070-strict:
	RELEASE_GATE_EXPECTED_CARGO_VERSION=0.9.2 python3 tools/release/gates/validate_release_gates_070.py --mode strict

# Lightweight local mirror of the CI `release-gates` job (pytest suites only;
# 41 gate + 12 matrix test files). Catches release-gate/matrix regressions
# locally before CI does.
release-pytest-check:
	PYTHONPATH=. python3 -m pytest -q tools/release/matrix/tests/
	PYTHONPATH=. python3 -m pytest -q tools/release/gates/tests/

# release-gates-check-070: comprehensive v0.7.0 release readiness gate.
# (The 070 gate packages the CURRENT 0.9.2 version by default —
# the gate name is the baseline lineage, not the packaged version; every
# release-gates-check-0XX target intentionally packages the current version.
# Override with PKG_VERSION= to validate a specific version line.)
#
# Environment variables:
#   RELEASE_GATE_ALLOW_SKIP_FUZZ=1       - skip fuzz smoke/build when
#                                           cargo +nightly is unavailable
#   RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 - skip native E2E when NGINX_BIN
#                                           is not set
#   RELEASE_GATE_REQUIRE_PACKAGES=0      - skip install-layout validation
#   PKG_VERSION / NGINX_VERSION          - override package/NGINX versions

release-gates-check-070:
	$(MAKE) build
	$(MAKE) check-headers
	$(MAKE) test-rust
	$(MAKE) test-nginx-unit
	@if cargo +nightly --version >/dev/null 2>&1; then \
	$(MAKE) test-rust-fuzz-smoke; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_FUZZ:-0}" = "1" ]; then \
	echo "==> SKIP (non-release): test-rust-fuzz-smoke (cargo nightly not available; RELEASE_GATE_ALLOW_SKIP_FUZZ=1)"; \
	else \
	echo "FAIL: test-rust-fuzz-smoke requires cargo +nightly; set RELEASE_GATE_ALLOW_SKIP_FUZZ=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	@if [ -n "$$NGINX_BIN" ]; then \
	$(MAKE) verify-chunked-native-e2e-smoke; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E:-0}" = "1" ]; then \
	echo "==> SKIP (non-release): verify-chunked-native-e2e-smoke (NGINX_BIN not set; RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1)"; \
	else \
	echo "FAIL: verify-chunked-native-e2e-smoke requires NGINX_BIN; set RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	$(MAKE) test-e2e-rust
	python3 tools/release/gates/validate_release_gates_070.py --mode strict
	python3 tools/release/gates/validate_config_directives.py
	$(MAKE) reason-codegen-check
	python3 tools/release/gates/validate_package_metadata.py
	python3 tools/release/gates/validate_k8s_manifests.py
	python3 tools/release/gates/validate_fuzz_packaging.py
	@echo "=== Package Compatibility Gate ==="
	@echo "  [artifact-naming-tests] Running artifact naming unit tests..."
	@bash tools/release/gates/test_artifact_naming.sh || \
	{ echo "FAIL: artifact naming tests failed" >&2; exit 1; }
	@echo "  [compat-check-tests] Running compat-check helper tests..."
	@if test -f tools/compat-check/test_compat_check.sh; then \
	bash tools/compat-check/test_compat_check.sh || \
		{ echo "FAIL: compat-check helper tests failed" >&2; exit 1; }; \
	else \
	echo "  SKIP: tools/compat-check/test_compat_check.sh not found"; \
	fi
	@echo "  [install-layout] Validating install layout..."
	@if test -f tools/release/gates/check_install_layout.sh; then \
		if ! ls dist/*.deb dist/*.rpm >/dev/null 2>&1; then \
			if [ "$${RELEASE_GATE_REQUIRE_PACKAGES:-1}" = "0" ]; then \
				echo "  SKIP: no package files in dist/ (RELEASE_GATE_REQUIRE_PACKAGES=0)"; \
				exit 0; \
			fi; \
			if command -v nfpm >/dev/null 2>&1; then \
				if ! test -f build/ngx_http_markdown_filter_module.so; then \
					echo "FAIL: install-layout requires build/ngx_http_markdown_filter_module.so but it was not found." >&2; \
					echo "  The 'make build' target only builds the Rust static library, not the NGINX dynamic module." >&2; \
					echo "  To fix, either:" >&2; \
					echo "    1. Place pre-built packages in dist/ (from release workflow artifacts), or" >&2; \
					echo "    2. Build the NGINX module .so first (requires NGINX source), or" >&2; \
					echo "    3. Set RELEASE_GATE_REQUIRE_PACKAGES=0 to skip install-layout validation." >&2; \
					exit 1; \
				fi; \
				host_arch="$$(uname -m)"; \
				if test "$$(uname -s)" = "Darwin" && \
					test "$$(sysctl -n hw.optional.arm64 2>/dev/null || echo 0)" = "1"; then \
					host_arch="arm64"; \
				fi; \
				case "$$host_arch" in \
					x86_64) nfpm_arch="amd64"; rpm_arch="x86_64" ;; \
					arm64|aarch64) nfpm_arch="arm64"; rpm_arch="aarch64" ;; \
					*) echo "FAIL: unsupported package build architecture: $$host_arch" >&2; exit 1 ;; \
				esac; \
				echo "  [install-layout] No packages found; building local $$nfpm_arch DEB/RPM with nFPM..."; \
				mkdir -p dist; \
				pkg_version="$${PKG_VERSION:-0.9.2}"; \
				nginx_version="$${NGINX_VERSION:-1.26.3}"; \
				nginx_version_ceil="$$(awk 'BEGIN { split(ARGV[1], p, "."); printf "%d.%d.%d", p[1], p[2], p[3] + 1 }' "$$nginx_version")"; \
				rpm_nginx_evr="$${RPM_NGINX_EVR:-1:$$nginx_version}"; \
				rpm_nginx_evr_ceil="$${RPM_NGINX_EVR_CEIL:-1:$$nginx_version_ceil}"; \
				nfpm_preinstall="$$(mktemp "$${TMPDIR:-/tmp}/nginx-markdown-preinstall.XXXXXX")"; \
				nfpm_preremove="$$(mktemp "$${TMPDIR:-/tmp}/nginx-markdown-preremove.XXXXXX")"; \
				nfpm_config="$$(mktemp "$${TMPDIR:-/tmp}/nginx-markdown-nfpm.XXXXXX")"; \
				trap 'rm -f "$$nfpm_preinstall" "$$nfpm_preremove" "$$nfpm_config"' EXIT; \
				packaging/nfpm/scripts/render-nfpm-config.sh \
					packaging/nfpm/scripts/preinstall.sh "$$nfpm_preinstall" "$$nginx_version"; \
				packaging/nfpm/scripts/render-nfpm-config.sh \
					packaging/nfpm/scripts/preremove.sh "$$nfpm_preremove" "$$nginx_version"; \
				sed -e "s|./packaging/nfpm/scripts/preinstall.sh|$$nfpm_preinstall|" \
				    -e "s|./packaging/nfpm/scripts/preremove.sh|$$nfpm_preremove|" \
					packaging/nfpm/nfpm.yaml > "$$nfpm_config"; \
				PKG_VERSION="$$pkg_version" NGINX_VERSION="$$nginx_version" \
					NGINX_VERSION_CEIL="$$nginx_version_ceil" \
					RPM_NGINX_EVR="$$rpm_nginx_evr" NFPM_ARCH="$$nfpm_arch" \
					nfpm package --config "$$nfpm_config" --packager deb \
					--target "dist/nginx-module-markdown-for-agents_$${pkg_version}_nginx-$${nginx_version}_$${nfpm_arch}.deb"; \
				PKG_VERSION="$$pkg_version" NGINX_VERSION="$$nginx_version" \
					NGINX_VERSION_CEIL="$$nginx_version_ceil" \
					RPM_NGINX_EVR="$$rpm_nginx_evr" RPM_NGINX_EVR_CEIL="$$rpm_nginx_evr_ceil" NFPM_ARCH="$$nfpm_arch" \
					nfpm package --config "$$nfpm_config" --packager rpm \
					--target "dist/nginx-module-markdown-for-agents-$${pkg_version}-nginx$${nginx_version}-1.$${rpm_arch}.rpm"; \
			else \
				echo "FAIL: no package files in dist/ and nfpm is unavailable." >&2; \
				echo "  To fix, either:" >&2; \
				echo "    1. Place pre-built packages in dist/ (from release workflow artifacts), or" >&2; \
				echo "    2. Install nfpm and build the NGINX module .so, or" >&2; \
				echo "    3. Set RELEASE_GATE_REQUIRE_PACKAGES=0 to skip install-layout validation." >&2; \
				exit 1; \
			fi; \
		fi; \
		if ls dist/*.deb dist/*.rpm >/dev/null 2>&1; then \
			bash tools/release/gates/check_install_layout.sh dist/*.deb dist/*.rpm || \
				{ echo "FAIL: install layout validation failed" >&2; exit 1; }; \
		else \
			echo "FAIL: no package files in dist/" >&2; \
			exit 1; \
		fi; \
	else \
		echo "  SKIP: tools/release/gates/check_install_layout.sh not found"; \
	fi
	@echo "  [postinst-safety] Validating postinst safety..."
	@if test -f tools/release/gates/check_postinst_safety.sh; then \
	bash tools/release/gates/check_postinst_safety.sh || \
		{ echo "FAIL: postinst safety validation failed" >&2; exit 1; }; \
	else \
	echo "  SKIP: tools/release/gates/check_postinst_safety.sh not found"; \
	fi
	@echo "  [preinstall-version-policy] Validating exact NGINX version policy..."
	@if test -f packaging/tests/test-preinstall-version-policy.sh; then \
	bash packaging/tests/test-preinstall-version-policy.sh || \
		{ echo "FAIL: preinstall exact-version policy test failed" >&2; exit 1; }; \
	else \
	echo "  SKIP: packaging/tests/test-preinstall-version-policy.sh not found"; \
	fi
	@echo "  [package-removal-guard] Validating fail-closed removal lifecycle..."
	@if test -f packaging/tests/test-package-removal-guard.sh; then \
	bash packaging/tests/test-package-removal-guard.sh || \
		{ echo "FAIL: package removal guard test failed" >&2; exit 1; }; \
	else \
	echo "FAIL: packaging/tests/test-package-removal-guard.sh not found" >&2; exit 1; \
	fi
	@echo "  [executable-trust] Validating maintainer script executable trust..."
	@if test -f packaging/tests/test-maintainer-script-executable-trust.sh; then \
	bash packaging/tests/test-maintainer-script-executable-trust.sh || \
		{ echo "FAIL: maintainer script executable trust test failed" >&2; exit 1; }; \
	else \
	echo "  SKIP: packaging/tests/test-maintainer-script-executable-trust.sh not found"; \
	fi
	@echo "  Package Compatibility Gate: ALL PASSED"
	@echo "=== Fuzz CI Gate ==="
	@if cargo +nightly --version >/dev/null 2>&1; then \
	echo "  [fuzz-build] cargo +nightly fuzz build..."; \
	cd $(RUST_DIR) && cargo +nightly fuzz build; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_FUZZ:-0}" = "1" ]; then \
	echo "  [fuzz-build] SKIP (non-release): cargo nightly not available (RELEASE_GATE_ALLOW_SKIP_FUZZ=1)"; \
	else \
	echo "FAIL: fuzz build requires cargo +nightly; set RELEASE_GATE_ALLOW_SKIP_FUZZ=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	@if cargo +nightly --version >/dev/null 2>&1; then \
	echo "  [fuzz-smoke] Running fuzz smoke (10s × 3 representative targets)..."; \
	cd $(RUST_DIR) && for target in convert_html streaming_chunks negotiation_and_headers; do \
		echo "    fuzzing $$target (10s)..."; \
		cargo +nightly fuzz run "$$target" -- -max_total_time=10; \
	done; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_FUZZ:-0}" = "1" ]; then \
	echo "  [fuzz-smoke] SKIP (non-release): cargo nightly not available (RELEASE_GATE_ALLOW_SKIP_FUZZ=1)"; \
	else \
	echo "FAIL: fuzz smoke requires cargo +nightly; set RELEASE_GATE_ALLOW_SKIP_FUZZ=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	@echo "  [cflite-workflows] Checking ClusterFuzzLite workflow files..."
	@test -f .github/workflows/cflite_pr.yml || { echo "FAIL: .github/workflows/cflite_pr.yml not found" >&2; exit 1; }
	@test -f .github/workflows/cflite_batch.yml || { echo "FAIL: .github/workflows/cflite_batch.yml not found" >&2; exit 1; }
	@test -f .github/workflows/cflite_cron.yml || { echo "FAIL: .github/workflows/cflite_cron.yml not found" >&2; exit 1; }
	@echo "  [fuzz-guide] Checking fuzz README completeness..."
	@test -f fuzz/README.md || { echo "FAIL: fuzz/README.md not found" >&2; exit 1; }
	@grep -q "Corpus Classification" fuzz/README.md || { echo "FAIL: fuzz/README.md missing section: Corpus Classification" >&2; exit 1; }
	@grep -q "FUZZ-001" fuzz/README.md || { echo "FAIL: fuzz/README.md missing section: FUZZ-001" >&2; exit 1; }
	@echo "  Fuzz CI Gate: ALL PASSED"
	@echo "=== Release Workflow Gate ==="
	@echo "  [release-workflow] Checking release-packages.yml exists..."
	@test -f .github/workflows/release-packages.yml || { echo "FAIL: .github/workflows/release-packages.yml not found" >&2; exit 1; }
	@echo "  [sha256sums] Checking SHA256SUMS generation logic..."
	@grep -q 'SHA256SUMS\|generate-checksums' .github/workflows/release-packages.yml || { echo "FAIL: release-packages.yml missing SHA256SUMS generation logic" >&2; exit 1; }
	@echo "  [smoke-test-job] Checking package smoke test job exists..."
	@grep -q 'smoke-test\|smoke_test' .github/workflows/release-packages.yml || { echo "FAIL: release-packages.yml missing smoke test job" >&2; exit 1; }
	@echo "  Release Workflow Gate: ALL PASSED"
	@echo "=== Documentation Gate ==="
	@echo "  [docs-check] Running docs-check for fuzz guide and install/compat docs..."
	@$(MAKE) docs-check
	@echo "  Documentation Gate: ALL PASSED"
	@echo "=== Harness Rule Coverage Gate ==="
	@echo "  [fuzz-rules] Verifying FUZZ-001 through FUZZ-007 defined in fuzz/README.md..."
	@for rule in FUZZ-001 FUZZ-002 FUZZ-003 FUZZ-004 FUZZ-005 FUZZ-006 FUZZ-007; do \
	grep -q "$$rule" fuzz/README.md || { echo "FAIL: fuzz/README.md missing rule $$rule" >&2; exit 1; }; \
	done
	@echo "  [harness-fuzz-check] Verifying harness-check covers fuzz infrastructure..."
	@$(MAKE) harness-check
	@echo "  Harness Rule Coverage Gate: ALL PASSED"

# release-gates-check-080: comprehensive v0.8.x release readiness gate.
#
# Coverage policy source: AGENTS.md Rule 25
#   - 80% aggregate line+function coverage (programmatic gate via coverage_gate.py)
#   - 90% for critical paths: auth, error handling, FFI boundary, conditional
#     requests (blocking via tools/ci/coverage_gate.py)
#
# Clean-checkout boundary (Req 9):
#   This gate runs from a clean git checkout without requiring:
#   - .kiro/ directories (user-local Kiro/spec state)
#   - .codeartsdoer/ directories (adapter caches)
#   - Any generated files outside the repository checkout
#   All Python scripts called by this gate consume only repo-owned inputs:
#   tools/, docs/, components/, AGENTS.md, .github/workflows/
#
# CI coverage:
#   - Streaming tests: covered by rust-quality job (make test-rust --all-features)
#   - Chunked native E2E: covered by runtime-regressions job
#   - Matrix validation: covered by docs-check job (make docs-check) and
#     matrix-release-tests job
#
# Classification (Req 6):
#   BLOCKING: All 17 sub-checks below are release-blocking.
#   EXPERIMENTAL (non-blocking, not in this gate):
#     - Performance benchmarks (perf-artifacts, perf-smoke in CI)
#     - Nightly fuzz beyond smoke (nightly-fuzz.yml)
#     - Docker integration tests (official-nginx-docker.yml)
#
# Sub-checks (all blocking):
#   1. make build + make check-headers        — Rust lib builds, header in sync
#   2. make test-rust                         — Rust unit + doctests + streaming tests (Req 1)
#   3. make test-nginx-unit                   — C state machine + config parsing tests (Req 1, 2)
#   4. make test-rust-fuzz-smoke              — Fuzz smoke (skippable, see env var below)
#   5. verify-chunked-native-e2e-smoke        — Native NGINX chunked/no-CL E2E (Req 1.2)
#   6. make test-e2e-rust                     — Rust E2E harness scenarios (Req 1)
#   7. make coverage-c                        — C coverage gate per AGENTS.md (Req 4)
#   8. make coverage-rust                     — Rust coverage gate per AGENTS.md (Req 4)
#   9. make docs-check                        — Documentation consistency (Req 3)
#  10. matrix validate_workflow_matrix_consumers — Matrix schema + CI coverage (Req 5)
#  11. make harness-check                     — Harness truth surface validation (Req 9)
#  12. current schema and contract validators — Release gate framework
#  13. validate_naming.py                     — Artifact naming consistency
#  14. v0.8.0 gate validators (0.8-specific: compat bridge removal, new directives)
#  15. legacy/prior-version regression validators (0.7.0 gates remain active)
#  16. Harness boundary: routing-manifest.json + risk-packs + core.md + README.md exist (Req 9)
#  17. Clean-checkout boundary verification (no user-local state required)
#
# Environment variables:
#   RELEASE_GATE_ALLOW_SKIP_FUZZ=1       - skip fuzz smoke when
#                                           cargo +nightly is unavailable
#   RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 - skip native E2E when NGINX_BIN
#                                           is not set
#   RELEASE_GATE_ALLOW_SKIP_COVERAGE=1   - skip coverage-c/coverage-rust
#                                           when NGINX source or lcov/cargo-llvm-cov
#                                           is unavailable

RELEASE_GATE_080_ACTIVE_VERSION ?= 0.8.3

release-gates-check-080:
	@echo "=== v0.8.x Release Gate: Starting ($(RELEASE_GATE_080_ACTIVE_VERSION)) ==="
	@echo "  [1/17] build + check-headers"
	$(MAKE) build
	$(MAKE) check-headers
	@echo "  [2/17] test-rust (includes streaming tests — Req 1)"
	$(MAKE) test-rust
	@echo "  [3/17] test-nginx-unit (state machine + config parsing — Req 1, 2)"
	$(MAKE) test-nginx-unit
	@echo "  [4/17] test-rust-fuzz-smoke"
	@if cargo +nightly --version >/dev/null 2>&1; then \
	$(MAKE) test-rust-fuzz-smoke; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_FUZZ:-0}" = "1" ]; then \
	echo "  ==> SKIP (non-release): test-rust-fuzz-smoke (cargo nightly not available; RELEASE_GATE_ALLOW_SKIP_FUZZ=1)"; \
	else \
	echo "FAIL: test-rust-fuzz-smoke requires cargo +nightly; set RELEASE_GATE_ALLOW_SKIP_FUZZ=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	@echo "  [5/17] verify-chunked-native-e2e-smoke (native NGINX chunked/no-CL — Req 1.2)"
	@if [ -n "$$NGINX_BIN" ]; then \
	$(MAKE) verify-chunked-native-e2e-smoke; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E:-0}" = "1" ]; then \
	echo "  ==> SKIP (non-release): verify-chunked-native-e2e-smoke (NGINX_BIN not set; RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1)"; \
	else \
	echo "FAIL: verify-chunked-native-e2e-smoke requires NGINX_BIN; set RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi
	@echo "  [6/17] test-e2e-rust (Rust E2E harness — Req 1)"
	$(MAKE) test-e2e-rust
	@echo "  [7/17] coverage-c (C coverage gate — Req 4)"
	@echo "  Policy source: AGENTS.md Rule 25 — 80% aggregate; 90% critical paths (blocking)"
	@set -e; \
	coverage_skipped=0; \
	if command -v lcov >/dev/null 2>&1 && [ -d "$(NGINX_TEST_DIR)" ]; then \
	$(MAKE) coverage-c; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_COVERAGE:-0}" = "1" ]; then \
	echo "  ==> SKIP (non-release): coverage-c (lcov or NGINX test dir not available; RELEASE_GATE_ALLOW_SKIP_COVERAGE=1)"; \
	coverage_skipped=1; \
	else \
	echo "FAIL: coverage-c requires lcov and NGINX test dir; set RELEASE_GATE_ALLOW_SKIP_COVERAGE=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi; \
	echo "  [8/17] coverage-rust (Rust coverage gate — Req 4)"; \
	echo "  Policy source: AGENTS.md Rule 25 — 80% aggregate; 90% critical paths (blocking)"; \
	if cargo llvm-cov --version >/dev/null 2>&1; then \
	$(MAKE) coverage-rust; \
	else \
	if [ "$${RELEASE_GATE_ALLOW_SKIP_COVERAGE:-0}" = "1" ]; then \
	echo "  ==> SKIP (non-release): coverage-rust (cargo-llvm-cov not available; RELEASE_GATE_ALLOW_SKIP_COVERAGE=1)"; \
	coverage_skipped=1; \
	else \
	echo "FAIL: coverage-rust requires cargo-llvm-cov; set RELEASE_GATE_ALLOW_SKIP_COVERAGE=1 to skip for non-release validation" >&2; exit 1; \
	fi; \
	fi; \
	echo "  [coverage-policy] Thresholds: C line=$(COVERAGE_C_MIN_LINE)% func=$(COVERAGE_C_MIN_FUNC)% | Rust line=$(COVERAGE_RUST_MIN_LINE)% func=$(COVERAGE_RUST_MIN_FUNC)%"; \
	echo "  [critical-path-coverage] 90% target for auth, error handling, FFI boundary, conditional requests"; \
	if [ "$$coverage_skipped" -eq 0 ]; then \
	echo "  [critical-path-coverage] 90% critical-path coverage is enforced by coverage_gate.py."; \
	python3 tools/ci/coverage_gate.py \
	--c-lcov $(COVERAGE_DIR)/c-coverage.lcov \
	--rust-lcov $(COVERAGE_DIR)/rust-coverage.lcov \
	--rust-streaming-lcov $(COVERAGE_DIR)/rust-streaming-coverage.lcov \
	--c-min-line $(COVERAGE_C_MIN_LINE) \
	--c-min-func $(COVERAGE_C_MIN_FUNC) \
	--rust-min-line $(COVERAGE_RUST_MIN_LINE) \
	--rust-min-func $(COVERAGE_RUST_MIN_FUNC) \
	--critical-path-min $(COVERAGE_CRITICAL_MIN); \
	else \
	echo "  ==> SKIP (non-release): aggregate coverage gate (component coverage was skipped; RELEASE_GATE_ALLOW_SKIP_COVERAGE=1)"; \
	fi
	@echo "  [9/17] docs-check (documentation consistency — Req 3)"
	$(MAKE) docs-check
	@echo "  [10/17] matrix validate_workflow_matrix_consumers (Req 5)"
	python3 tools/release/matrix/validate_workflow_matrix_consumers.py
	@echo "  [11/17] harness-check (harness truth surface — Req 9)"
	$(MAKE) harness-check
	@echo "  [12/17] current schema and contract validators"
	python3 tools/release/gates/validate_schema_drift.py --version "$(SCHEMA_RELEASE_VERSION)"
	@echo "  [13/17] validate_naming.py (artifact naming)"
	python3 tools/release/gates/validate_naming.py
	@echo "  [14/17] v0.8.0 gate validators (0.8-specific: compat bridge removal, new directives)"
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_release_gates_080.py
	python3 tools/release/gates/validate_config_directives.py
	@echo "  [15/17] legacy/prior-version regression validators (0.7.0 gates remain active)"
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_release_gates_070.py --mode strict
	$(MAKE) reason-codegen-check
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_package_metadata.py
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_k8s_manifests.py
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_fuzz_packaging.py
	@echo "  [16/17] harness boundary: routing-manifest.json + risk-packs (Req 9)"
	@test -f docs/harness/routing-manifest.json || { echo "FAIL: docs/harness/routing-manifest.json not found — release gates require repo-owned harness sources" >&2; exit 1; }
	@test -d docs/harness/risk-packs || { echo "FAIL: docs/harness/risk-packs/ not found — release gates require repo-owned risk-pack inputs" >&2; exit 1; }
	@test -f docs/harness/core.md || { echo "FAIL: docs/harness/core.md not found — release gates require repo-owned harness sources (Req 9.3)" >&2; exit 1; }
	@test -f docs/harness/README.md || { echo "FAIL: docs/harness/README.md not found — release gates require repo-owned harness sources (Req 9.3)" >&2; exit 1; }
	@echo "  [17/17] clean-checkout boundary verification"
	@echo "  All validators use only repo-owned sources (no .kiro/, no .codeartsdoer/, no adapter caches)."
	@echo "  Inputs: tools/, docs/, components/, AGENTS.md, Makefile, .github/workflows/"
	@echo "  This gate is reproducible from a clean 'git clone' without user-local state."
	@if [ -d ".kiro/specs" ] && grep -r "\.kiro/" tools/release/ tools/harness/ 2>/dev/null | grep -v "\.kiro/steering" | grep -qv "^$$"; then \
		echo "WARNING: potential .kiro/ reference found in gate validators (review needed)" >&2; \
	fi
	@echo "=== v0.8.x Release Gate: ALL PASSED ($(RELEASE_GATE_080_ACTIVE_VERSION)) ==="

release-gates-check-08x: release-gates-check-080
	@echo "  (release-gates-check-08x is an alias for release-gates-check-080, the 0.8.x patch-line gate)"

# 0.8.0 regression subset — version-independent stable checks from 0.8 gate.
# Skips RELEASE_GATE_EXPECTED_CARGO_VERSION-bound validators so 0.9+ branches
# can inherit the 0.8 regression surface without a version-number conflict.
release-gates-check-080-regression:
	@echo "=== v0.8.x Regression Subset (version-independent) ==="
	$(MAKE) build
	$(MAKE) check-headers
	$(MAKE) test-rust
	$(MAKE) test-nginx-unit
	$(MAKE) test-e2e-rust
	$(MAKE) docs-check
	python3 tools/release/matrix/validate_workflow_matrix_consumers.py
	$(MAKE) harness-check
	python3 tools/release/gates/validate_schema_drift.py --version 0.9.2
	python3 tools/release/gates/validate_naming.py
	python3 tools/release/gates/validate_config_directives.py
	@test -f docs/harness/routing-manifest.json
	@test -d docs/harness/risk-packs
	@test -f docs/harness/core.md
	@test -f docs/harness/README.md
	@echo "=== v0.8.x Regression Subset: ALL PASSED ==="

# perf-evidence-check: Non-blocking performance evidence gate (report-only).
# Runs the module-level benchmark harness and evaluates against thresholds.
# Exits 0 regardless of verdict (soft/report-only mode).
# Reports SKIP_NOT_PRESENT and exits 0 when NGINX_BIN is unavailable.
# Reports MISSING_EVIDENCE and exits 0 when the checked-in baseline does not
# match the current platform, load generator, or NGINX version; incompatible
# environments are never used for percentage regression comparisons.
#
# Evidence pack includes: module benchmark tiers, decompression coverage,
# fallback rate, memory slope.
#
# Classification: SOFT (report-only, does not fail the build)
perf-evidence-check:
	@echo "=== Performance Evidence Check (non-blocking) ==="
	@tools/perf/run_evidence_gate.sh

# release-perf-evidence-blocking: shared helper that runs the module-level
# performance evidence gate in blocking mode against a caller-specified
# baseline.  Each release-gates-check-0NN recipe must invoke this helper with
# an explicit BASELINE_VERSION so the baseline enters the blocking decision
# instead of being merely exported and silently ignored.
#
# Parameter:
#   BASELINE_VERSION - the module baseline to evaluate (e.g. 091, 092)
#
# Environment variables (forwarded to evidence_gate.py):
#   NGINX_BIN                            - Path to module-enabled nginx binary
#   RELEASE_GATE_ALLOW_SKIP_MODULE=1     - Allow proceeding without module
#                                           benchmarks (non-release only)
#
# Classification: BLOCKING (fails on NO_GO/MISSING_EVIDENCE; fail-closed when
# NGINX_BIN is absent unless an explicit skip is authorized). A baseline whose
# policy explicitly sets release_gate_eligible=false is provenance-validated
# and intentionally excluded until its recorded remeasurement condition is met.
.PHONY: release-perf-evidence-blocking
release-perf-evidence-blocking:
	@if [ -z "$(BASELINE_VERSION)" ]; then \
		echo "FAIL: release-perf-evidence-blocking requires BASELINE_VERSION" >&2; \
		exit 1; \
	fi
	@echo "  Performance evidence gate (blocking mode, baseline $(BASELINE_VERSION))"
	@if [ -n "$${NGINX_BIN:-}" ]; then \
		MODULE_BASELINE_VERSION="$(BASELINE_VERSION)" \
			EVIDENCE_GATE_MODE=blocking EVIDENCE_GATE_ALLOW_SKIP_MODULE=0 \
				tools/perf/run_evidence_gate.sh || exit 1; \
	else \
		if [ "$${RELEASE_GATE_ALLOW_SKIP_MODULE:-0}" = "1" ]; then \
			MODULE_BASELINE_VERSION="$(BASELINE_VERSION)" \
			EVIDENCE_GATE_MODE=blocking EVIDENCE_GATE_ALLOW_SKIP_MODULE=1 \
					tools/perf/run_evidence_gate.sh || exit 1; \
		else \
			echo "FAIL: Module-level benchmarks require NGINX_BIN." >&2; \
			echo "  Set NGINX_BIN=/path/to/nginx or RELEASE_GATE_ALLOW_SKIP_MODULE=1 to skip." >&2; \
			exit 1; \
		fi; \
	fi

# release-gates-check-092-canonical: Blocking 0.9.2 performance/contract gate.
# This is the current release entry point.  The former 0.9.0/0.9.1 target
# chain is consolidated here so one gate owns the active release contract.
# Package artifact registry, candidate-bound final evidence, fuzz qualification,
# and soak qualification remain in release-gates-check-092 and the canonical
# release-packages.yml workflow, where their required inputs are available.
#
# It retains the useful 0.9.x regression checks: production examples,
# complexity, threshold/performance evidence, and the focused 0.7/0.8
# compatibility validators.  The old release-chain wrappers are gone.
#
# Environment variables:
#   NGINX_BIN                            - Path to module-enabled nginx binary
#   RELEASE_GATE_ALLOW_SKIP_MODULE=1     - Allow proceeding without module
#                                           benchmarks
#   CANDIDATE_BENCHMARK_REPORT            - Optional repository-relative 092
#                                           report retained from the candidate
#                                           baseline measurement.  It is used
#                                           only by the 092 stage; the 091
#                                           prerequisite always measures its
#                                           own independent report.
#   RELEASE_GATE_ALLOW_SKIP_FUZZ=1       - (inherited) skip fuzz smoke
#   RELEASE_GATE_ALLOW_SKIP_NATIVE_E2E=1 - (inherited) skip native E2E
#   RELEASE_GATE_ALLOW_SKIP_COVERAGE=1   - (inherited) skip coverage
#
# Classification: BLOCKING
release-gates-check-092-canonical: release-gates-check-080-regression
	@echo "=== 0.9.2 Canonical Performance/Contract Gates (blocking) ==="
	@echo "  [1/8] Consolidated 0.9.x regression and compatibility gates"
	$(MAKE) release-matrix-check
	$(MAKE) test-production-examples-nginx-t
	$(MAKE) test-production-examples-e2e-smoke
	$(MAKE) complexity-check
	python3 -c "from tools.perf.threshold_engine import evaluate_module_level; print('  threshold_engine module-level: OK')"
	$(MAKE) release-perf-evidence-blocking BASELINE_VERSION=091
	@if python3 -c "import pytest, hypothesis" >/dev/null 2>&1; then \
		python3 -m pytest tools/perf/tests/ -q --tb=short; \
	else \
		echo "FAIL: pytest/hypothesis dependencies are missing. Please install them using: pip install -r requirements-dev.txt" >&2; \
		exit 1; \
	fi
	RELEASE_GATE_EXPECTED_CARGO_VERSION=0.9.2 python3 tools/release/gates/validate_release_gates_070.py --mode strict
	python3 tools/release/gates/validate_release_gates_080.py --active-version 0.9.2
	@echo "  [2/8] 0.9.2 performance evidence gate (blocking mode, baseline 092)"
	@if [ -n "$(CANDIDATE_BENCHMARK_REPORT)" ]; then \
		echo "  Reusing candidate-bound 092 benchmark report: $(CANDIDATE_BENCHMARK_REPORT)"; \
	fi
	EVIDENCE_GATE_BENCHMARK_REPORT="$(CANDIDATE_BENCHMARK_REPORT)" \
		$(MAKE) release-perf-evidence-blocking BASELINE_VERSION=092
	@echo "  [3/8] Public surface and schema drift checks"
	$(MAKE) public-surface-drift-check
	$(MAKE) schema-drift-check SCHEMA_RELEASE_VERSION=0.9.2
	@echo "  [4/8] Version consistency (0.9.2)"
	bash tools/harness/detect_version_consistency.sh
	@echo "  [5/8] Reason code registry completeness"
	$(MAKE) reason-codegen-check
	python3 tools/release/gates/validate_official_feature_manifest.py
	$(MAKE) reason-codegen-generate
	$(MAKE) reason-codegen-check
	PYTHONPATH=. python3 tools/release/gates/validate_release_gates_092.py
	@echo "  [6/8] Streaming lifecycle unit test"
	$(MAKE) -C $(NGINX_TEST_DIR) unit-streaming_impl
	@echo "  [7/8] Official build feature manifest"
	$(MAKE) official-feature-manifest-generate
	python3 tools/release/gates/validate_official_feature_manifest.py
	git diff --exit-code -- \
		components/rust-converter/src/decision/reason_code.rs \
		artifacts/release/0.9.2/reason-registry-report.json \
		artifacts/release/0.9.2/generated-reason-artifacts.json \
		artifacts/release/0.9.2/official-build-feature-manifest.json
	@echo "  [8/8] Canonical release matrix"
	PYTHONPATH=. python3 tools/release/matrix/validate_release_matrix.py
	@echo "=== 0.9.2 Canonical Performance/Contract Gates: PASS ==="

# release-gates-check-092: Complete blocking 0.9.2 release gate.
# The canonical performance/contract subset above is followed by the
# candidate-bound artifact, evidence, fuzz, soak, docs, and property gates.
# Those stages must remain here so a package release cannot pass on a
# performance-only workflow's partial evidence.
release-gates-check-092: release-gates-check-092-canonical
	@echo "=== 0.9.2 Release Gates (blocking) ==="
	@echo "  [extended 1/9] Release tag ref protection (immutable v* tags)"
	# A published tag anchors source archives, provenance, and signatures to
	# the approved candidate; an unprotected tag can be moved or deleted after
	# publication, splitting provenance.  Fails closed when the API is
	# unreachable.  See tools/release/gates/verify_tag_ref_protection.py.
	python3 tools/release/gates/verify_tag_ref_protection.py
	@echo "  [extended 2/9] Streaming parity evidence"
	$(MAKE) streaming-evidence-check
	@echo "  [extended 3/9] Release candidate evidence bound to HEAD"
	# The candidate-bound manifests are workflow outputs (generated from
	# tracked policy/scope inputs at release time), not tracked working-tree
	# state.  Generate the inputs phase locally so this gate can close from a
	# clean checkout — mirroring the "Generate candidate-bound release gate
	# inputs" step in release-packages.yml.  The output path is gitignored.
	python3 tools/release/gates/generate_release_gate_manifests.py \
		--phase inputs --candidate-sha "$$(git rev-parse HEAD)"
	$(MAKE) release-candidate-evidence-check
	@echo "  [extended 4/9] Artifact registry evidence"
	$(MAKE) artifact-registry-check
	@echo "  [extended 5/9] Release evidence manifest"
	$(MAKE) release-evidence-manifest-check
	@echo "  [extended 6/9] Fuzz qualification evidence"
	$(MAKE) test-rust-fuzz-qualification
	@echo "  [extended 7/9] Soak qualification evidence"
	$(MAKE) test-e2e-rust-soak
	@echo "  [extended 8/9] Repository docs style baseline"
	$(MAKE) docs-style-check-baseline
	@echo "  [extended 9/9] Property-based test suites (Rust proptest + Python Hypothesis)"
	$(MAKE) test-property
	@echo "=== 0.9.2 Release Gates: PASS ==="

# streaming-evidence-check: Blocking parity evidence and registry contract.
streaming-evidence-check:
	@echo "=== Streaming Evidence Check ==="
	@set -e; \
		summary_path="$$(mktemp "$${TMPDIR:-/tmp}/nginx-markdown-streaming-evidence.XXXXXX")"; \
		trap 'rm -f "$$summary_path"' EXIT HUP INT TERM; \
		python3 tools/release/gates/generate_streaming_evidence.py \
			--output "$$summary_path"; \
		python3 tools/release/gates/validate_streaming_evidence.py \
			"$$summary_path" --git-head

# release-matrix-check: Release matrix source/projection gate.
# Checks that docs/releases/release-matrix.json is the deterministic
# release-contract projection of tools/release-matrix.json, then validates
# its immutable schema, ABI binding, and feature-manifest binding. Fail-closed
# on any source or projection drift.
# Classification: BLOCKING
release-matrix-check:
	@echo "=== Release Matrix Source/Projection Check ==="
	python3 tools/release/matrix/generate_release_contract_matrix.py --check
	python3 tools/release/gates/validate_official_feature_manifest.py
	$(MAKE) official-feature-manifest-generate
	python3 tools/release/gates/validate_official_feature_manifest.py
	git diff --exit-code -- artifacts/release/0.9.2/official-build-feature-manifest.json
	PYTHONPATH=. python3 tools/release/matrix/validate_release_matrix.py
	PYTHONPATH=. python3 -m pytest tools/release/matrix/tests/test_validate_release_matrix.py -q --tb=short
	@echo "  Release Matrix Source/Projection Check: PASSED"

# pre-lts-status-check: Validate the six release-level statuses without
# promoting pending, blocked, fixture, or external-observation work to PASS.
# The candidate.source_sha records the frozen candidate; during development
# HEAD advances past it, so this routine check validates schema and
# vocabulary only.  Freeze flows call the validator with --git-head to fail
# closed when the recorded SHA drifts from the frozen HEAD.
pre-lts-status-check:
	python3 tools/release/gates/validate_pre_lts_status.py

# release-candidate-evidence-check: Pre-freeze release candidate evidence gate.
# Validates release-candidate evidence against the v1 schema.
# FIXTURE mode: make release-candidate-evidence-check FIXTURE=path/to/fixture.json
release-candidate-evidence-check:
	@echo "=== Release Candidate Evidence Check ==="
	@if [ -n "$(FIXTURE)" ]; then \
		python3 tools/release/gates/validate_release_candidate_evidence.py --mode fixture --expected-sha 9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d --record-input "$(FIXTURE)"; \
	else \
		python3 tools/release/gates/validate_release_candidate_evidence.py --mode real --git-head; \
	fi

# artifact-registry-check: Pre-freeze artifact registry gate.
# Validates artifact registry against the v1 schema.
# FIXTURE mode: make artifact-registry-check FIXTURE=path/to/fixture.json
artifact-registry-check:
	@echo "=== Artifact Registry Check ==="
	@if [ -n "$(FIXTURE)" ]; then \
		python3 tools/release/gates/validate_artifact_registry.py --mode fixture --expected-sha 9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d --record-input "$(FIXTURE)"; \
	else \
		python3 tools/release/gates/validate_artifact_registry.py --mode real; \
	fi

# release-evidence-manifest-check: Pre-freeze release evidence manifest gate.
# Validates release evidence manifest against the v1 schema and, in real
# mode, requires the manifest candidate_sha to equal the repository HEAD
# (--verify-head) so committed evidence cannot drift from the release
# candidate (candidate identity binding: evidence SHA == release candidate SHA).
# FIXTURE mode: make release-evidence-manifest-check FIXTURE=path/to/fixture.json
release-evidence-manifest-check:
	@echo "=== Release Evidence Manifest Check ==="
	@if [ -n "$(FIXTURE)" ]; then \
		python3 tools/release/gates/validate_release_evidence_manifest.py --mode fixture --expected-sha 9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d9d --record-input "$(FIXTURE)"; \
	else \
		python3 tools/release/gates/validate_release_evidence_manifest.py --mode real --verify-head; \
	fi

# test-rust-fuzz-qualification: Pre-freeze fuzz qualification gate.
# Validates fuzz qualification evidence against manifest thresholds.
# FIXTURE mode: make test-rust-fuzz-qualification FIXTURE=path/to/fixture.json
test-rust-fuzz-qualification:
	@echo "=== Fuzz Qualification Check ==="
	@if [ -n "$(FIXTURE)" ]; then \
		python3 tools/release/gates/validate_fuzz_qualification.py --mode fixture --manifest tests/fixtures/release/fuzz-qualification-manifest.json --record-input "$(FIXTURE)"; \
	else \
		python3 tools/release/gates/validate_fuzz_qualification.py --mode real; \
	fi

# test-e2e-rust-soak: Pre-freeze soak qualification gate.
# Validates soak qualification evidence against manifest thresholds.
# FIXTURE mode: make test-e2e-rust-soak FIXTURE=path/to/fixture.json
test-e2e-rust-soak:
	@echo "=== Soak Qualification Check ==="
	@if [ -n "$(FIXTURE)" ]; then \
		python3 tools/release/gates/validate_soak_qualification.py --mode fixture --manifest tests/fixtures/release/soak-qualification-manifest.json --record-input "$(FIXTURE)"; \
	else \
		python3 tools/release/gates/validate_soak_qualification.py --mode real; \
	fi

release-gates-check-all: release-gates-check release-gates-check-092
	@echo "=== Release Gates: ALL PASS ==="

# Production Examples + Migration Guide: validate all example configs pass
# nginx -t (NEW)
# Requires a module-enabled NGINX binary (NGINX_BIN or PATH nginx).
# When the binary is unavailable, the gate fails unless
# RELEASE_GATE_ALLOW_SKIP_MODULE=1 is set, mirroring the 091
# module-benchmark skip contract.  This is an environment limitation skip
# (non-release validation only); tag-release CI must provide NGINX_BIN.
test-production-examples-nginx-t: SHELL := /bin/bash
test-production-examples-nginx-t:
	@echo "=== Examples and Migration Guide nginx -t ==="
	@nginx_bin="$${NGINX_BIN:-nginx}"; \
	module_so="$${MODULE_SO:-}"; \
	if command -v "$$nginx_bin" >/dev/null 2>&1; then \
		NGINX_BIN="$$(command -v "$$nginx_bin")" \
		MODULE_SO="$$module_so" \
			bash tools/e2e/verify_examples_nginx_t.sh || exit 1; \
	else \
		if [ "$${RELEASE_GATE_ALLOW_SKIP_MODULE:-0}" = "1" ]; then \
			echo "SKIP: nginx binary not found (set NGINX_BIN to a module-enabled nginx; RELEASE_GATE_ALLOW_SKIP_MODULE=1)"; \
		else \
			echo "FAIL: nginx binary not found (set NGINX_BIN to a module-enabled nginx)" >&2; \
			echo "  Set NGINX_BIN=/path/to/nginx or RELEASE_GATE_ALLOW_SKIP_MODULE=1 to skip." >&2; \
			exit 1; \
		fi; \
	fi

# Production Examples: E2E smoke (requires running NGINX, deferred)
test-production-examples-e2e-smoke:
	@echo "=== Production Examples E2E Smoke ==="
	@bash tools/e2e/verify_profile_smoke_e2e.sh || { \
		rc=$$?; \
		case "$$rc" in \
			2) \
				if [ "$${RELEASE_GATE_ALLOW_SKIP_MODULE:-0}" = "1" ]; then \
					echo "SKIP: E2E smoke requires NGINX_BIN environment (deferred to CI; RELEASE_GATE_ALLOW_SKIP_MODULE=1)"; \
				else \
					echo "FAIL: E2E smoke requires NGINX_BIN environment (set NGINX_BIN to a module-enabled nginx)" >&2; \
					exit "$$rc"; \
				fi ;; \
			*) exit "$$rc" ;; \
		esac; \
	}

release-gates-check-070-docker:
	@echo "=== Gate 3/4 Local Docker Validation ==="
	@echo "  Running Gate 3 (Package Distribution) and Gate 4 (K8s) locally via Docker..."
	RELEASE_GATE_LOCAL_DOCKER=1 python3 tools/release/gates/validate_release_gates_070.py --mode strict

release-gates-check-strict:
	$(MAKE) release-gates-check
	RELEASE_GATE_EXPECTED_CARGO_VERSION=$(RELEASE_GATE_080_ACTIVE_VERSION) python3 tools/release/gates/validate_release_gates_070.py --mode strict
	python3 tools/release/gates/validate_fuzz_packaging.py
	@echo "=== Strict: Release Workflow Gate ==="
	@test -f .github/workflows/release-packages.yml || { echo "FAIL: .github/workflows/release-packages.yml not found" >&2; exit 1; }
	@grep -q 'SHA256SUMS\|generate-checksums' .github/workflows/release-packages.yml || { echo "FAIL: release-packages.yml missing SHA256SUMS generation logic" >&2; exit 1; }
	@grep -q 'smoke-test\|smoke_test' .github/workflows/release-packages.yml || { echo "FAIL: release-packages.yml missing smoke test job" >&2; exit 1; }
	@echo "  Release Workflow Gate: ALL PASSED"
	@echo "=== Strict: Documentation Gate ==="
	@$(MAKE) docs-check
	@echo "  Documentation Gate: ALL PASSED"
	@echo "=== Strict: Harness Rule Coverage Gate ==="
	@for rule in FUZZ-001 FUZZ-002 FUZZ-003 FUZZ-004 FUZZ-005 FUZZ-006 FUZZ-007; do \
	grep -q "$$rule" fuzz/README.md || { echo "FAIL: fuzz/README.md missing rule $$rule" >&2; exit 1; }; \
	done
	@$(MAKE) harness-check
	@echo "  Harness Rule Coverage Gate: ALL PASSED"

verify-large-e2e:
	./tools/e2e/verify_large_markdown_response_e2e.sh

verify-huge-native-e2e:
	./tools/e2e/verify_huge_body_native_e2e.sh

verify-huge-allowed-native-e2e:
	./tools/e2e/verify_huge_body_allowed_native_e2e.sh

verify-chunked-native-e2e:
	./tools/e2e/verify_chunked_streaming_native_e2e.sh

verify-chunked-native-e2e-smoke:
	./tools/e2e/verify_chunked_streaming_native_e2e.sh --profile smoke

verify-chunked-native-e2e-stress:
	./tools/e2e/verify_chunked_streaming_native_e2e.sh --profile stress

verify-brotli-streaming-e2e:
	./tools/e2e/verify_brotli_streaming_e2e.sh

verify-http2-alpn-e2e:
	./tools/e2e/verify_http2_alpn_e2e.sh

verify-encoding-chain-e2e:
	./tools/e2e/verify_encoding_chain_e2e.sh

verify-streaming-failure-cache-e2e:
	./tools/e2e/verify_streaming_failure_cache_e2e.sh $(E2E_ARGS)

verify-streaming-failure-cache-e2e-plan:
	./tools/e2e/verify_streaming_failure_cache_e2e.sh --plan

verify-metrics-endpoint-e2e:
	./tools/e2e/verify_metrics_endpoint_e2e.sh

verify-conditional-requests-e2e:
	./tools/e2e/verify_conditional_requests_e2e.sh

# Real-NGINX IMS validation — mirrors the real-nginx-ims.yml CI job
# (and the macOS smoke job).  The CI job builds its own module-enabled
# NGINX; locally the script reuses the NGINX_BIN passed to test-all-e2e.
# Skipped when only NGINX_URL (a running fixture) is supplied because the
# script manages its own NGINX lifecycle and cannot attach to an external
# one.
verify-real-nginx-ims-e2e:
	@if test "$(SKIP)" = "1"; then \
		echo "SKIP: real-NGINX IMS validation skipped explicitly (SKIP=1)" >&2; \
	elif test -z "$(NGINX_BIN)"; then \
		echo "FAIL: real-NGINX IMS validation requires NGINX_BIN (NGINX_URL fixture mode not supported); set SKIP=1 to skip explicitly" >&2; \
		exit 1; \
	else \
		NGINX_BIN="$(NGINX_BIN)" bash tools/ci/verify_real_nginx_ims.sh --port 18088; \
	fi

# SSI / auth_request / filter-ordering / internal-redirect qualification with a
# real module-enabled NGINX — mirrors the "Run native SSI and filter-ordering
# qualification" step of the CI runtime-regressions job.
verify-subrequest-filter-ordering-native-e2e:
	@if test "$(SKIP)" = "1"; then \
		echo "SKIP: filter-ordering native E2E skipped explicitly (SKIP=1)" >&2; \
	elif test -z "$(NGINX_BIN)"; then \
		echo "FAIL: filter-ordering native E2E requires NGINX_BIN (NGINX_URL fixture mode not supported); set SKIP=1 to skip explicitly" >&2; \
		exit 1; \
	else \
		REQUIRE_FILTER_ORDERING_ALL=1 REQUIRE_AUTH_SUBREQUEST=1 \
			bash tools/e2e/verify_subrequest_filter_ordering_native_e2e.sh --nginx-bin "$(NGINX_BIN)" --port 18099; \
	fi

# Non-streaming production-module linkage check — builds the Rust archive
# without optional features, compiles and links every NGINX module source and
# passes `nginx -t`.  Self-contained (downloads and builds its own NGINX), so
# it runs unconditionally; mirrors the CI "Verify non-streaming production
# module linkage" step.
verify-non-streaming-module-e2e:
	bash tools/ci/verify_non_streaming_nginx_module.sh

verify-config-merge-e2e:
	./tools/e2e/verify_config_merge_e2e.sh

verify-auth-cache-e2e:
	./tools/e2e/verify_auth_cache_e2e.sh

verify-status-codes-e2e:
	./tools/e2e/verify_status_codes_e2e.sh

verify-diagnostics-access-phase-e2e:
	./tools/e2e/verify_diagnostics_access_phase_e2e.sh

# ── Coverage targets ────────────────────────────────────────────────
# Generate lcov reports consumed by SonarCloud.  Output lands in
# coverage/ at the repo root so sonar.coverageReportPaths can find it.

COVERAGE_DIR := coverage

coverage-c:
	@command -v lcov >/dev/null 2>&1 || { echo "ERROR: lcov is required for coverage-c but not found in PATH" >&2; exit 1; }
	@mkdir -p $(COVERAGE_DIR) $(COVERAGE_DIR)/tmp
	tools/sonar/collect_nginx_coverage.sh --output $(CURDIR)/$(COVERAGE_DIR)/tmp/c-e2e-coverage.lcov
	$(MAKE) -C $(NGINX_TEST_DIR) unit-coverage COV_DIR=$(CURDIR)/$(COVERAGE_DIR)/tmp/c-unit
	lcov --add-tracefile $(COVERAGE_DIR)/tmp/c-e2e-coverage.lcov \
	--add-tracefile $(COVERAGE_DIR)/tmp/c-unit/c-coverage.lcov \
	--output-file $(COVERAGE_DIR)/tmp/c-combined-raw.lcov \
	--rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors inconsistent,inconsistent --ignore-errors count,count
	lcov --extract $(COVERAGE_DIR)/tmp/c-combined-raw.lcov \
	"*/components/nginx-module/src/*" \
	--output-file $(COVERAGE_DIR)/c-coverage.lcov \
	--rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors unused,unused --ignore-errors inconsistent,inconsistent
	@echo "==> Combined C coverage report: $(COVERAGE_DIR)/c-coverage.lcov"
	lcov --summary $(COVERAGE_DIR)/c-coverage.lcov --rc branch_coverage=1 \
	--ignore-errors inconsistent,inconsistent
	@tools/sonar/check_advisory_coverage.sh $(COVERAGE_DIR)/c-coverage.lcov

coverage-rust:
	@mkdir -p $(COVERAGE_DIR)
	cd $(RUST_DIR) && cargo llvm-cov --lcov \
	--output-path $(CURDIR)/$(COVERAGE_DIR)/rust-coverage.lcov \
	-- --skip report_contains_all_tiers \
	   --skip legacy_single_large \
	   --skip full_run_report
	cd $(RUST_DIR) && cargo llvm-cov --features streaming --lcov \
	--output-path $(CURDIR)/$(COVERAGE_DIR)/rust-streaming-coverage.lcov \
	-- --skip report_contains_all_tiers \
	   --skip legacy_single_large \
	   --skip full_run_report

# Convert all lcov reports to SonarQube Generic Coverage XML.
# sonar.coverageReportPaths expects this format, not raw lcov.
coverage-sonar-xml:
	python3 tools/sonar/lcov_to_sonar_xml.py \
	-o $(COVERAGE_DIR)/sonar-coverage.xml \
	$(wildcard $(COVERAGE_DIR)/*.lcov)

coverage-all: coverage-c coverage-rust coverage-sonar-xml

COVERAGE_C_MIN_LINE ?= 80
COVERAGE_C_MIN_FUNC ?= 80
COVERAGE_RUST_MIN_LINE ?= 80
COVERAGE_RUST_MIN_FUNC ?= 80
COVERAGE_CRITICAL_MIN ?= 90

coverage-gate: coverage-c coverage-rust
	python3 tools/ci/coverage_gate.py \
	--c-lcov $(COVERAGE_DIR)/c-coverage.lcov \
	--rust-lcov $(COVERAGE_DIR)/rust-coverage.lcov \
	--rust-streaming-lcov $(COVERAGE_DIR)/rust-streaming-coverage.lcov \
	--c-min-line $(COVERAGE_C_MIN_LINE) \
	--c-min-func $(COVERAGE_C_MIN_FUNC) \
	--rust-min-line $(COVERAGE_RUST_MIN_LINE) \
	--rust-min-func $(COVERAGE_RUST_MIN_FUNC) \
	--critical-path-min $(COVERAGE_CRITICAL_MIN)

clean:
	cd $(RUST_DIR) && cargo clean
	$(MAKE) -C $(NGINX_TEST_DIR) clean || true
	# Remove only the known generated build-output directories; never scan
	# the whole module tree, which may contain developer-owned directories.
	rm -rf build
	rm -rf $(NGINX_TEST_DIR)/build
	rm -rf coverage

help:
	@echo "NGINX Markdown for Agents - Build/Test"
	@echo ""
	@echo "Targets:"
	@echo "  build                    - Build Rust library + sync header"
	@echo "  capability-check         - Fail closed when a promised engine or encoding is missing"
	@echo "  test                     - Fast smoke tests"
	@echo "  rust-fmt-check           - Check Rust and corpus-tool formatting"
	@echo "  rust-clippy-check        - Run Clippy for Rust and corpus-tool crates"
	@echo "  test-rust                - Run Rust test suite (unit + doctests)"
	@echo "  test-rust-streaming      - Run Rust streaming feature tests"
	@echo "  test-rust-doc            - Run Rust doctests only (all features)"
	@echo "  test-rust-fuzz-smoke     - Run short cargo-fuzz smoke checks"
	@echo "  test-nginx-unit          - Run nginx C unit tests"
	@echo "  test-nginx-unit-streaming - Run nginx C streaming unit tests"
	@echo "  test-nginx-unit-clang-smoke - Run nginx C smoke tests with clang"
	@echo "  test-nginx-unit-sanitize-smoke - Run nginx C smoke tests with ASan/UBSan"
	@echo "  test-nginx-integration   - Run integration tests"
	@echo "  test-e2e                 - Run end-to-end tests"
	@echo "  test-e2e-rust            - Build and run Rust e2e-harness migrated scenarios"
	@echo "  verify-streaming-failure-cache-e2e - Run streaming failure/cache e2e tests"
	@echo "  verify-streaming-failure-cache-e2e-plan - Print test plan only (no NGINX_BIN required)"
	@echo "  verify-metrics-endpoint-e2e  - Run Prometheus text 0.0.4 metrics E2E tests"
	@echo "  verify-conditional-requests-e2e - Run conditional-request e2e tests (ETag/304)"
	@echo "  verify-config-merge-e2e     - Run config-merge e2e tests (http/server/location)"
	@echo "  verify-auth-cache-e2e       - Run auth/cache interaction e2e tests"
	@echo "  verify-status-codes-e2e     - Run upstream status-code passthrough e2e tests"
	@echo "  verify-diagnostics-access-phase-e2e - Verify native NGINX access-phase restricts diagnostics/metrics handlers"
	@echo "  test-all                 - Run build + rust + unit tests"
	@echo "  sonar-compile-db         - Generate compile_commands.json for SonarQube for VS Code C/C++ analysis"
	@echo "  test-benchmark           - Run corpus benchmark and produce Unified Report"
	@echo "  test-benchmark-compare   - Compare corpus reports (baseline vs current)"
	@echo "  test-benchmark-summary   - Generate PR benchmark summary from latest report"
	@echo "  harness-check            - Validate harness truth surfaces and optional local adapters"
	@echo "  harness-check-full       - Run full harness validation plus docs/releases checks"
	@echo "  harness-security-checks  - Run local static harness/security detectors"
	@echo "  release-supply-chain-check - Validate immutable release inputs, secret scope, and auth transport"
	@echo "  complexity-check         - Run complexity analysis (lizard + complexipy + shellcheck)"
	@echo "  security-static          - Run actionlint, shellcheck, gitleaks, Semgrep, and cargo-deny"
	@echo "  supply-chain             - Run Trivy filesystem/IaC scan and generate a Syft SPDX SBOM"
	@echo "  test-harness             - Run unit tests for harness detector scripts"
	@echo "  regex-security-check     - Run ReDoS/regex detector and regression tests"
	@echo "  e2e-streaming-config-check - Validate Rule 60 E2E nginx configurations"
	@echo "  sonar-encoding-check     - Validate UTF-8 source files and declared exceptions"
	@echo "  docs-check               - Validate documentation links/style"
	@echo "  license-check            - Verify license policy and THIRD-PARTY-NOTICES coverage"
	@echo "  release-gates-check      - Validate the current release contract baseline"
	@echo "  release-gates-check-070  - Validate 0.7.0 release gates (runtime correctness, package compat, fuzz)"
	@echo "  release-gates-check-080  - Validate 0.8.x release gates (streaming, coverage, matrix, harness boundary)"
	@echo "  release-gates-check-080-regression - 0.8.x version-independent regression subset (no Cargo version bind)"
	@echo "  release-gates-check-08x  - Alias for release-gates-check-080 (0.8.x patch-line canonical entry)"
	@echo "  release-gates-check-092-canonical - Validate consolidated 0.9.x/0.9.2 performance and contract gates"
	@echo "  release-gates-check-092  - Validate complete 0.9.2 release gates (blocking; candidate evidence + artifacts + fuzz + soak)"
	@echo "  release-matrix-check      - Validate canonical release matrix against the checked-in schema and ABI/feature bindings"
	@echo "  release-candidate-evidence-check - Validate frozen release candidate SHA manifest (FIXTURE=... for regression fixtures)"
	@echo "  artifact-registry-check   - Validate candidate-bound release artifact index (FIXTURE=... for regression fixtures)"
	@echo "  release-evidence-manifest-check - Validate final evidence manifest + observation state (FIXTURE=... for regression fixtures)"
	@echo "  test-rust-fuzz-qualification   - Fuzz qualification gate (15min or 100k execs per blocking target; FIXTURE=... for regression fixtures)"
	@echo "  test-e2e-rust-soak        - Short-soak qualification gate (30min concurrency 16; FIXTURE=... for regression fixtures)"
	@echo "  release-perf-evidence-blocking - Shared blocking evidence helper (requires BASELINE_VERSION)"
	@echo "  perf-evidence-check      - Run performance evidence gate (non-blocking, report-only)"
	@echo "  release-gates-check-all  - Run current baseline and 0.9.2 release gates"
	@echo "  release-gates-check-strict - Validate all sub-specs #12-#18 for full compliance"
	@echo "  test-production-examples-nginx-t - Validate example configs and migration-guide examples pass nginx -t"
	@echo "  test-production-examples-e2e-smoke - Production examples E2E smoke (deferred to CI)"
	@echo "  release-notes            - Generate release notes from release-matrix.json"
	@echo "  coverage-c               - Generate C module e2e coverage (builds NGINX with --coverage)"
	@echo "  coverage-rust            - Generate Rust test coverage (llvm-cov lcov)"
	@echo "  coverage-all             - Generate all coverage reports"
	@echo "  coverage-gate            - Generate coverage and enforce min thresholds (default 80%%)"
	@echo "  clean                    - Clean build artifacts"
