#!/bin/sh
set -eu
umask 022

gate_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
root=$(CDPATH= cd -- "$gate_dir/../../../.." && pwd -P)
input=$gate_dir/P15-038-input.json
base=
output=

while test "$#" -gt 0; do
	case "$1" in
	--base)
		test "$#" -ge 2 || { printf '%s\n' '--base requires a commit' >&2; exit 2; }
		base=$2
		shift 2
		;;
	--output)
		test "$#" -ge 2 || { printf '%s\n' '--output requires a directory' >&2; exit 2; }
		output=$2
		shift 2
		;;
	*)
		printf 'unknown argument: %s\n' "$1" >&2
		exit 2
		;;
	esac
done

test -n "$base" || { printf '%s\n' '--base is required' >&2; exit 2; }
test -n "$output" || { printf '%s\n' '--output is required' >&2; exit 2; }
test -f "$input" || { printf 'missing input: %s\n' "$input" >&2; exit 2; }
for tool in cc git pkg-config python3 sha256sum; do
	command -v "$tool" >/dev/null 2>&1 || {
		printf 'missing required host tool: %s\n' "$tool" >&2
		exit 2
	}
done
case "$output" in
/*) ;;
*) output=$PWD/$output ;;
esac
test ! -e "$output" || { printf 'refusing existing output: %s\n' "$output" >&2; exit 2; }
mkdir -p "$output"

PYTHONDONTWRITEBYTECODE=1 python3 -B - "$root" "$input" "$base" "$output" <<'PY'
from __future__ import annotations

import hashlib
import json
import os
from pathlib import Path
import statistics
import subprocess
import sys
import tempfile
import threading
from typing import Any


ROOT = Path(sys.argv[1])
INPUT = Path(sys.argv[2])
REQUESTED_BASE = sys.argv[3]
OUTPUT = Path(sys.argv[4])
SPEC = json.loads(INPUT.read_text(encoding="ascii"))


class GateFailure(RuntimeError):
    def __init__(self, status: str, reason: str):
        super().__init__(reason)
        self.status = status
        self.reason = reason


def sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def sha256_path(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for block in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()


def write_json(path: Path, value: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="ascii")


def run(
    argv: list[str], *, timeout: int, log: Path | None = None,
    allowed: set[int] | None = None,
) -> subprocess.CompletedProcess[str]:
    try:
        completed = subprocess.run(
            argv,
            check=False,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            timeout=timeout,
        )
    except subprocess.TimeoutExpired as error:
        if log is not None:
            log.write_text(
                "$ " + " ".join(argv) + f"\n[TIMEOUT after {timeout}s]\n",
                encoding="utf-8",
            )
        raise GateFailure("INFRA_BLOCKED", f"timeout after {timeout}s: {' '.join(argv)}") from error
    if log is not None:
        log.write_text(
            "$ " + " ".join(argv) + "\n" + completed.stdout +
            f"\n[exit {completed.returncode}]\n",
            encoding="utf-8",
        )
    expected = {0} if allowed is None else allowed
    if completed.returncode not in expected:
        raise GateFailure(
            "RUNNER_FAIL",
            f"command failed ({completed.returncode}): {' '.join(argv)}",
        )
    return completed


def git(*args: str) -> str:
    return run(["git", "-C", str(ROOT), *args], timeout=30).stdout.strip()


def source_at(commit: str, relative: str) -> bytes:
    completed = run(
        ["git", "-C", str(ROOT), "show", f"{commit}:{relative}"],
        timeout=30,
    )
    return completed.stdout.encode("utf-8")


def verify_identity() -> tuple[str, dict[str, Any]]:
    if SPEC.get("schema") != 1 or SPEC.get("candidate") != "P15-038":
        raise GateFailure("INFRA_BLOCKED", "invalid P15-038 input identity")
    if SPEC.get("gate") != "G05" or SPEC.get("scope") != (
        "host-codec-cost-oracle-not-guest-vnode-performance"
    ):
        raise GateFailure("INFRA_BLOCKED", "invalid G05 scope")
    resolved = git("rev-parse", f"{REQUESTED_BASE}^{{commit}}")
    if resolved != SPEC["required_base"]:
        raise GateFailure(
            "INFRA_BLOCKED",
            f"P15-038 must replay {SPEC['required_base']}, got {resolved}",
        )
    hashes = {
        relative: sha256_bytes(source_at(resolved, relative))
        for relative in SPEC["source_sha256"]
    }
    if hashes != SPEC["source_sha256"]:
        raise GateFailure("INFRA_BLOCKED", "frozen B32/source identity changed")
    package_names = {
        "liblz4": "liblz4",
        "liblzma": "liblzma",
        "zlib": "zlib",
        "libzstd": "libzstd",
    }
    versions = {
        name: run(["pkg-config", "--modversion", package], timeout=30).stdout.strip()
        for name, package in package_names.items()
    }
    if versions != SPEC["libraries"]:
        raise GateFailure(
            "INFRA_BLOCKED",
            f"host codec library identity changed: {versions!r}",
        )
    zdata = source_at(resolved, "repo-pre-15/src/zdata.c").decode("utf-8")
    internal = source_at(resolved, "repo-pre-15/src/internal.h").decode("utf-8")
    required_tokens = (
        "cache->nid == vi->nid",
        "cache->decoded_size == decoded_size",
        "cache->map.m_pa == map->m_pa",
        "cache->map.m_la == map->m_la",
        "cache->map.m_plen == map->m_plen",
        "cache->map.m_llen == map->m_llen",
        "cache->map.m_deviceid == map->m_deviceid",
        "cache->map.m_algorithmformat == map->m_algorithmformat",
        "cache->map.m_flags == map->m_flags",
        "cache->state == EROFS_ZCACHE_INFLIGHT || cache->waiters != 0",
        "cache->error = error",
        "cache->closing = true",
        "Z_EROFS_CACHE_BYPASS",
    )
    missing = [token for token in required_tokens if token not in zdata]
    if missing:
        raise GateFailure("STOP", f"B32 key/inflight/failure contract is not closed: {missing!r}")
    if zdata.count("map->m_algorithmformat == Z_EROFS_COMPRESSION_LZMA") != 1:
        raise GateFailure("STOP", "frozen admission is not exactly LZMA-only")
    for token in ("EROFS_ZCACHE_EMPTY", "EROFS_ZCACHE_INFLIGHT", "EROFS_ZCACHE_READY", "EROFS_ZCACHE_FAILED"):
        if token not in internal:
            raise GateFailure("STOP", f"B32 state is missing: {token}")
    return resolved, {"libraries": versions, "source_sha256": hashes}


class Budget:
    def __init__(self, limit: int):
        self.limit = limit
        self.used = 0
        self.peak = 0
        self.lock = threading.Lock()

    def reserve(self, amount: int) -> bool:
        with self.lock:
            if amount > self.limit - self.used:
                return False
            self.used += amount
            self.peak = max(self.peak, self.used)
            return True

    def release(self, amount: int) -> None:
        with self.lock:
            if amount > self.used:
                raise GateFailure("STOP", "global budget accounting underflow")
            self.used -= amount


class Cache:
    def __init__(self, budget: Budget, local_limit: int, minimum_work: int, enabled: bool = True):
        self.budget = budget
        self.local_limit = local_limit
        self.minimum_work = minimum_work
        self.enabled = enabled
        self.condition = threading.Condition()
        self.state = "EMPTY"
        self.key: tuple[int, ...] | None = None
        self.data: bytes | None = None
        self.error = 0
        self.waiters = 0
        self.charged = 0
        self.peak = 0
        self.closing = False
        self.owners = 0
        self.evictions = 0
        self.reclaims = 0
        self.bypasses = 0

    def eligible(self, decoded_size: int, decode_work: int) -> bool:
        return (
            self.enabled
            and decoded_size <= self.local_limit
            and decode_work >= self.minimum_work
        )

    def _release_locked(self) -> None:
        if self.charged:
            self.budget.release(self.charged)
            self.charged = 0
        self.data = None

    def claim(self, key: tuple[int, ...], decoded_size: int, decode_work: int) -> tuple[str, bytes | int | None]:
        if not self.eligible(decoded_size, decode_work):
            self.bypasses += 1
            return "BYPASS", None
        with self.condition:
            if self.closing:
                self.bypasses += 1
                return "BYPASS", None
            if self.state != "EMPTY" and self.key == key:
                if self.state == "READY":
                    if self.data is None:
                        raise GateFailure("STOP", "READY cache has no data")
                    return "HIT", self.data
                if self.state == "INFLIGHT":
                    self.waiters += 1
                    self.condition.notify_all()
                    while self.state == "INFLIGHT":
                        self.condition.wait()
                    if self.key != key:
                        raise GateFailure("STOP", "waiter generation key changed")
                    if self.state == "READY":
                        result: tuple[str, bytes | int | None] = ("HIT", self.data)
                    elif self.state == "FAILED" and self.error > 0:
                        result = ("ERROR", self.error)
                    else:
                        raise GateFailure("STOP", "waiter received untyped publication")
                    self.waiters -= 1
                    if self.state == "FAILED" and self.waiters == 0:
                        self.error = 0
                        self.state = "EMPTY"
                    if self.waiters == 0:
                        self.condition.notify_all()
                    return result
                if self.state == "FAILED":
                    if self.waiters:
                        self.bypasses += 1
                        return "BYPASS", None
                    self.error = 0
                    self.state = "EMPTY"
            if self.state == "INFLIGHT" or self.waiters:
                self.bypasses += 1
                return "BYPASS", None
            if self.state == "READY":
                self._release_locked()
                self.evictions += 1
                self.state = "EMPTY"
            if not self.budget.reserve(decoded_size):
                self.bypasses += 1
                return "BYPASS", None
            self.charged = decoded_size
            self.peak = max(self.peak, self.charged)
            self.key = key
            self.error = 0
            self.state = "INFLIGHT"
            self.owners += 1
            return "OWNER", None

    def wait_for_waiters(self, expected: int) -> None:
        with self.condition:
            if not self.condition.wait_for(lambda: self.waiters == expected, timeout=10):
                raise GateFailure("STOP", "same-key waiters did not register")

    def complete(self, key: tuple[int, ...], data: bytes | None, error: int) -> None:
        if (error == 0) != (data is not None):
            raise GateFailure("STOP", "owner completion is untyped")
        with self.condition:
            if self.state != "INFLIGHT" or self.key != key:
                raise GateFailure("STOP", "owner lost inflight key")
            if error == 0:
                assert data is not None
                if len(data) != self.charged:
                    raise GateFailure("STOP", "published bytes do not match reservation")
                self.data = data
                self.state = "READY"
            else:
                if error < 1:
                    raise GateFailure("STOP", "published errno is not positive")
                self._release_locked()
                self.error = error
                self.state = "FAILED"
                if self.waiters == 0:
                    self.error = 0
                    self.state = "EMPTY"
            self.condition.notify_all()

    def reclaim(self) -> str:
        with self.condition:
            if self.state == "INFLIGHT" or self.waiters:
                return "BUSY"
            if self.state == "READY":
                self._release_locked()
                self.state = "EMPTY"
                self.reclaims += 1
                return "RECLAIMED"
            return "EMPTY"

    def fini(self, closing: threading.Event | None = None) -> None:
        with self.condition:
            self.closing = True
            if closing is not None:
                closing.set()
            while self.state == "INFLIGHT" or self.waiters:
                self.condition.wait()
            self._release_locked()
            self.error = 0
            self.state = "EMPTY"


def payload(key: tuple[int, ...], size: int) -> bytes:
    seed = sum((index + 1) * value for index, value in enumerate(key)) & 0xFF
    return bytes(((seed + index * 29) & 0xFF) for index in range(size))


def run_wave(cache: Cache, key: tuple[int, ...], size: int, error: int) -> dict[str, Any]:
    workers = 16
    barrier = threading.Barrier(workers)
    results: list[tuple[str, bytes | int | None] | None] = [None] * workers
    expected = payload(key, size)

    def worker(index: int) -> None:
        barrier.wait(timeout=10)
        result = cache.claim(key, size, size * 2)
        if result[0] == "OWNER":
            cache.wait_for_waiters(workers - 1)
            cache.complete(key, None if error else expected, error)
            results[index] = ("ERROR", error) if error else ("HIT", expected)
        else:
            results[index] = result

    threads = [threading.Thread(target=worker, args=(index,)) for index in range(workers)]
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join(timeout=15)
    if any(thread.is_alive() for thread in threads):
        raise GateFailure("STOP", "state-model worker did not terminate")
    if error:
        if any(result != ("ERROR", error) for result in results):
            raise GateFailure("STOP", "same-key waiters did not receive one typed failure")
    elif any(result != ("HIT", expected) for result in results):
        raise GateFailure("STOP", "same-key waiters did not receive identical bytes")
    return {"error": error, "owners": 1, "waiters": workers - 1}


def run_state_model() -> dict[str, Any]:
    config = SPEC["budget"]
    extent = SPEC["benchmark"]["extent_bytes"]
    global_budget = Budget(config["global_bytes"])
    cache = Cache(
        global_budget,
        config["per_mount_bytes"],
        config["minimum_decode_work_bytes"],
    )
    key = (42, extent, 4096, 0, 8192, extent, 0, 1, 1)
    success = run_wave(cache, key, extent, 0)
    failure_key = (43, extent, 8192, 0, 8192, extent, 0, 2, 1)
    failure = run_wave(cache, failure_key, extent, 97)
    retry = cache.claim(failure_key, extent, extent * 2)
    if retry[0] != "OWNER":
        raise GateFailure("STOP", "typed failure is not retryable")
    retry_payload = payload(failure_key, extent)
    cache.complete(failure_key, retry_payload, 0)

    before = sha256_bytes(retry_payload)
    changed = list(failure_key)
    changed[2] += 4096
    changed_key = tuple(changed)
    result = cache.claim(changed_key, extent, extent * 2)
    if result[0] != "OWNER":
        raise GateFailure("STOP", "ready key eviction did not create an owner")
    cache.complete(changed_key, payload(changed_key, extent), 0)
    result = cache.claim(failure_key, extent, extent * 2)
    if result[0] != "OWNER":
        raise GateFailure("STOP", "evicted key reuse did not create a fresh owner")
    after_payload = payload(failure_key, extent)
    cache.complete(failure_key, after_payload, 0)
    after = sha256_bytes(after_payload)
    if before != after:
        raise GateFailure("STOP", "eviction changed decoded bytes")

    inflight_key = (44, extent, 12288, 0, 8192, extent, 0, 3, 1)
    if cache.claim(inflight_key, extent, extent * 2)[0] != "OWNER":
        raise GateFailure("STOP", "reclaim setup did not create owner")
    if cache.reclaim() != "BUSY":
        raise GateFailure("STOP", "reclaim did not refuse inflight state")
    fallback_key = (45, extent, 16384, 0, 8192, extent, 0, 0, 1)
    if cache.claim(fallback_key, extent, extent * 2)[0] != "BYPASS":
        raise GateFailure("STOP", "different-key inflight miss did not bypass")
    fallback_hash = sha256_bytes(payload(fallback_key, extent))
    cache.complete(inflight_key, payload(inflight_key, extent), 0)
    if cache.reclaim() != "RECLAIMED" or global_budget.used != 0:
        raise GateFailure("STOP", "ready reclaim did not release budget")

    first = Cache(global_budget, extent, config["minimum_decode_work_bytes"])
    second = Cache(global_budget, extent, config["minimum_decode_work_bytes"])
    if first.claim(key, extent, extent * 2)[0] != "OWNER":
        raise GateFailure("STOP", "global budget setup failed")
    first.complete(key, payload(key, extent), 0)
    if second.claim(changed_key, extent, extent * 2)[0] != "OWNER":
        raise GateFailure("STOP", "global budget should fit two reservations")
    second.complete(changed_key, payload(changed_key, extent), 0)
    third = Cache(global_budget, extent, config["minimum_decode_work_bytes"])
    if third.claim(fallback_key, extent, extent * 2)[0] != "BYPASS":
        raise GateFailure("STOP", "global exhaustion did not use no-cache fallback")
    exhausted_hash = sha256_bytes(payload(fallback_key, extent))
    if first.reclaim() != "RECLAIMED":
        raise GateFailure("STOP", "global reclaim did not free a reservation")
    if third.claim(fallback_key, extent, extent * 2)[0] != "OWNER":
        raise GateFailure("STOP", "reclaimed global budget was not reusable")
    third.complete(fallback_key, payload(fallback_key, extent), 0)
    second.fini()
    third.fini()

    shutdown = Cache(global_budget, extent, config["minimum_decode_work_bytes"])
    shutdown_key = (46, extent, 20480, 0, 8192, extent, 0, 1, 1)
    if shutdown.claim(shutdown_key, extent, extent * 2)[0] != "OWNER":
        raise GateFailure("STOP", "unmount setup did not create owner")
    closing = threading.Event()
    finished = threading.Event()

    def finish_cache() -> None:
        shutdown.fini(closing)
        finished.set()

    thread = threading.Thread(target=finish_cache)
    thread.start()
    if not closing.wait(timeout=10) or finished.is_set():
        raise GateFailure("STOP", "unmount did not wait for inflight owner")
    if shutdown.claim(changed_key, extent, extent * 2)[0] != "BYPASS":
        raise GateFailure("STOP", "closing cache accepted a new owner")
    shutdown.complete(shutdown_key, payload(shutdown_key, extent), 0)
    thread.join(timeout=10)
    if thread.is_alive() or not finished.is_set() or global_budget.used != 0:
        raise GateFailure("STOP", "unmount drain did not close cleanly")

    disabled = Cache(global_budget, extent, config["minimum_decode_work_bytes"], enabled=False)
    if disabled.claim(key, extent, extent * 2)[0] != "BYPASS":
        raise GateFailure("STOP", "disabled policy did not bypass")
    low_work = Cache(global_budget, extent, config["minimum_decode_work_bytes"])
    if low_work.claim(key, extent, config["minimum_decode_work_bytes"] - 1)[0] != "BYPASS":
        raise GateFailure("STOP", "low-cost request did not bypass")
    oversized = Cache(global_budget, extent // 2, config["minimum_decode_work_bytes"])
    if oversized.claim(key, extent, extent * 2)[0] != "BYPASS":
        raise GateFailure("STOP", "oversized request did not bypass")
    no_cache_hash = sha256_bytes(payload(key, extent))

    if global_budget.peak > global_budget.limit or cache.peak > cache.local_limit:
        raise GateFailure("STOP", "state model exceeded hard budget")
    return {
        "budget": {
            "global_limit": global_budget.limit,
            "global_peak": global_budget.peak,
            "global_remaining": global_budget.used,
            "mount_limit": cache.local_limit,
            "mount_peak": cache.peak,
        },
        "correctness": {
            "eviction_sha256_before": before,
            "eviction_sha256_after": after,
            "fallback_sha256": fallback_hash,
            "global_exhaustion_fallback_sha256": exhausted_hash,
            "no_cache_sha256": no_cache_hash,
        },
        "coverage": [
            "owner-waiter-success",
            "typed-failure-retry",
            "exact-key-reuse",
            "ready-eviction",
            "inflight-reclaim-busy",
            "ready-reclaim",
            "global-exhaustion-fallback",
            "unmount-drain",
            "disabled-policy",
            "low-work-bypass",
            "oversize-bypass",
        ],
        "failure_wave": failure,
        "lock_order": SPEC["lock_order"],
        "status": "PASS",
        "success_wave": success,
    }


BENCH_SOURCE = r'''
#define _POSIX_C_SOURCE 200809L
#include <inttypes.h>
#include <limits.h>
#include <lz4.h>
#include <lzma.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#include <zstd.h>

enum codec_id { CODEC_LZ4, CODEC_LZMA, CODEC_DEFLATE, CODEC_ZSTD, CODEC_COUNT };

struct fixture {
	enum codec_id id;
	const char *name;
	unsigned char *compressed;
	size_t compressed_size;
};

static void
fail(const char *message)
{
	fprintf(stderr, "P15-038 codec oracle failure: %s\n", message);
	exit(10);
}

static uint64_t
now_ns(void)
{
	struct timespec time;

	if (clock_gettime(CLOCK_MONOTONIC, &time) != 0)
		fail("clock_gettime");
	return ((uint64_t)time.tv_sec * UINT64_C(1000000000) + (uint64_t)time.tv_nsec);
}

static uint32_t
xorshift32(uint32_t *state)
{
	uint32_t value = *state;

	value ^= value << 13;
	value ^= value >> 17;
	value ^= value << 5;
	*state = value;
	return (value);
}

static uint64_t
hash_update(uint64_t hash, const unsigned char *data, size_t size)
{
	size_t index;

	for (index = 0; index < size; ++index) {
		hash ^= data[index];
		hash *= UINT64_C(1099511628211);
	}
	return (hash);
}

static void
write_file(const char *path, const void *data, size_t size)
{
	FILE *stream = fopen(path, "wb");

	if (stream == NULL || fwrite(data, 1, size, stream) != size || fclose(stream) != 0)
		fail("write fixture artifact");
}

static void
make_payload(unsigned char *payload, size_t size, uint32_t seed)
{
	unsigned char block[4096];
	size_t index;

	for (index = 0; index < sizeof(block); ++index)
		block[index] = (unsigned char)xorshift32(&seed);
	for (index = 0; index < size; ++index)
		payload[index] = block[index % sizeof(block)];
}

static struct fixture
compress_fixture(enum codec_id id, const char *name, const unsigned char *payload, size_t size)
{
	struct fixture fixture = { .id = id, .name = name };
	size_t capacity, output_position;
	uLongf zlib_size;
	int amount;

	switch (id) {
	case CODEC_LZ4:
		capacity = (size_t)LZ4_compressBound((int)size);
		fixture.compressed = malloc(capacity);
		amount = LZ4_compress_default((const char *)payload,
		    (char *)fixture.compressed, (int)size, (int)capacity);
		if (amount <= 0)
			fail("LZ4 compression");
		fixture.compressed_size = (size_t)amount;
		break;
	case CODEC_LZMA:
		capacity = lzma_stream_buffer_bound(size);
		fixture.compressed = malloc(capacity);
		output_position = 0;
		if (lzma_easy_buffer_encode(6, LZMA_CHECK_NONE, NULL, payload, size,
		    fixture.compressed, &output_position, capacity) != LZMA_OK)
			fail("LZMA compression");
		fixture.compressed_size = output_position;
		break;
	case CODEC_DEFLATE:
		capacity = (size_t)compressBound((uLong)size);
		fixture.compressed = malloc(capacity);
		zlib_size = (uLongf)capacity;
		if (compress2(fixture.compressed, &zlib_size, payload, (uLong)size, 6) != Z_OK)
			fail("Deflate compression");
		fixture.compressed_size = (size_t)zlib_size;
		break;
	case CODEC_ZSTD:
		capacity = ZSTD_compressBound(size);
		fixture.compressed = malloc(capacity);
		fixture.compressed_size = ZSTD_compress(
		    fixture.compressed, capacity, payload, size, 3);
		if (ZSTD_isError(fixture.compressed_size))
			fail("Zstd compression");
		break;
	default:
		fail("unknown compression codec");
	}
	if (fixture.compressed == NULL)
		fail("compressed allocation");
	return (fixture);
}

static void
decode(const struct fixture *fixture, unsigned char *output, size_t output_size)
{
	size_t input_position, output_position, amount;
	uint64_t memory_limit;
	uLongf zlib_size;
	int decoded;

	switch (fixture->id) {
	case CODEC_LZ4:
		decoded = LZ4_decompress_safe((const char *)fixture->compressed,
		    (char *)output, (int)fixture->compressed_size, (int)output_size);
		if (decoded != (int)output_size)
			fail("LZ4 decode");
		break;
	case CODEC_LZMA:
		memory_limit = UINT64_MAX;
		input_position = 0;
		output_position = 0;
		if (lzma_stream_buffer_decode(&memory_limit, 0, NULL,
		    fixture->compressed, &input_position, fixture->compressed_size,
		    output, &output_position, output_size) != LZMA_OK ||
		    input_position != fixture->compressed_size || output_position != output_size)
			fail("LZMA decode");
		break;
	case CODEC_DEFLATE:
		zlib_size = (uLongf)output_size;
		if (uncompress(output, &zlib_size, fixture->compressed,
		    (uLong)fixture->compressed_size) != Z_OK || zlib_size != output_size)
			fail("Deflate decode");
		break;
	case CODEC_ZSTD:
		amount = ZSTD_decompress(output, output_size,
		    fixture->compressed, fixture->compressed_size);
		if (ZSTD_isError(amount) || amount != output_size)
			fail("Zstd decode");
		break;
	default:
		fail("unknown decompression codec");
	}
}

static uint64_t
workload(const struct fixture *fixture, const unsigned char *payload,
    size_t extent_size, size_t read_size, const size_t *offsets, size_t reads,
    int neutral, size_t budget, size_t minimum_work, size_t *peak, uint64_t *hashp)
{
	unsigned char *cached = NULL, *decoded, *slice;
	uint64_t begin, end, hash = UINT64_C(1469598103934665603);
	size_t index;
	int admit;

	admit = (fixture->id == CODEC_LZMA || neutral) && extent_size <= budget &&
	    fixture->compressed_size + extent_size >= minimum_work;
	slice = malloc(read_size);
	if (slice == NULL)
		fail("slice allocation");
	*peak = 0;
	begin = now_ns();
	for (index = 0; index < reads; ++index) {
		if (cached == NULL) {
			decoded = malloc(extent_size);
			if (decoded == NULL)
				fail("decode allocation");
			decode(fixture, decoded, extent_size);
			memcpy(slice, decoded + offsets[index], read_size);
			if (admit) {
				cached = decoded;
				if (extent_size > *peak)
					*peak = extent_size;
			} else {
				free(decoded);
			}
		} else {
			memcpy(slice, cached + offsets[index], read_size);
		}
		if (memcmp(slice, payload + offsets[index], read_size) != 0)
			fail("random read bytes differ");
		hash = hash_update(hash, slice, read_size);
	}
	end = now_ns();
	free(cached);
	free(slice);
	*hashp = hash;
	return (end - begin);
}

static void
eviction_oracle(const struct fixture *first, const struct fixture *second,
    size_t extent_size, const char *directory)
{
	unsigned char *cache = malloc(extent_size);
	char path[PATH_MAX];

	if (cache == NULL)
		fail("eviction allocation");
	decode(first, cache, extent_size);
	snprintf(path, sizeof(path), "%s/eviction-before.bin", directory);
	write_file(path, cache, extent_size);
	free(cache);
	cache = malloc(extent_size);
	if (cache == NULL)
		fail("second eviction allocation");
	decode(second, cache, extent_size);
	free(cache);
	cache = malloc(extent_size);
	if (cache == NULL)
		fail("reused eviction allocation");
	decode(first, cache, extent_size);
	snprintf(path, sizeof(path), "%s/eviction-after.bin", directory);
	write_file(path, cache, extent_size);
	free(cache);
}

int
main(int argc, char **argv)
{
	static const char *names[CODEC_COUNT] = { "lz4", "lzma", "deflate", "zstd" };
	struct fixture fixtures[CODEC_COUNT];
	unsigned char *payload, *verification;
	size_t *offsets;
	size_t extent_size, read_size, reads, samples, budget, minimum_work;
	size_t codec, sample, peak_current, peak_candidate;
	uint32_t seed;
	uint64_t current_ns, candidate_ns, current_hash, candidate_hash;
	char path[PATH_MAX];

	if (argc != 9)
		fail("usage");
	extent_size = (size_t)strtoull(argv[1], NULL, 10);
	read_size = (size_t)strtoull(argv[2], NULL, 10);
	reads = (size_t)strtoull(argv[3], NULL, 10);
	samples = (size_t)strtoull(argv[4], NULL, 10);
	budget = (size_t)strtoull(argv[5], NULL, 10);
	minimum_work = (size_t)strtoull(argv[6], NULL, 10);
	seed = (uint32_t)strtoul(argv[7], NULL, 10);
	if (extent_size == 0 || extent_size > INT_MAX || read_size == 0 ||
	    read_size > extent_size || reads == 0 || samples != 5)
		fail("invalid fixed workload");
	payload = malloc(extent_size);
	verification = malloc(extent_size);
	offsets = malloc(reads * sizeof(*offsets));
	if (payload == NULL || verification == NULL || offsets == NULL)
		fail("workload allocation");
	make_payload(payload, extent_size, seed);
	for (codec = 0; codec < CODEC_COUNT; ++codec) {
		fixtures[codec] = compress_fixture((enum codec_id)codec, names[codec],
		    payload, extent_size);
		decode(&fixtures[codec], verification, extent_size);
		if (memcmp(payload, verification, extent_size) != 0)
			fail("full decode verification");
		snprintf(path, sizeof(path), "%s/%s.compressed", argv[8], names[codec]);
		write_file(path, fixtures[codec].compressed, fixtures[codec].compressed_size);
	}
	snprintf(path, sizeof(path), "%s/payload.bin", argv[8]);
	write_file(path, payload, extent_size);
	for (sample = 0; sample < reads; ++sample)
		offsets[sample] = (size_t)xorshift32(&seed) % (extent_size - read_size + 1);

	printf("{\"schema\":1,\"scope\":\"host-codec-cost-only\",\"samples\":%zu,\"reads_per_sample\":%zu,\"codecs\":[", samples, reads);
	for (codec = 0; codec < CODEC_COUNT; ++codec) {
		if (codec != 0)
			printf(",");
		printf("{\"codec\":\"%s\",\"compressed_bytes\":%zu,\"current_ns\":[",
		    fixtures[codec].name, fixtures[codec].compressed_size);
		for (sample = 0; sample < samples; ++sample) {
			if ((sample & 1) == 0) {
				current_ns = workload(&fixtures[codec], payload, extent_size,
				    read_size, offsets, reads, 0, budget, minimum_work,
				    &peak_current, &current_hash);
				candidate_ns = workload(&fixtures[codec], payload, extent_size,
				    read_size, offsets, reads, 1, budget, minimum_work,
				    &peak_candidate, &candidate_hash);
			} else {
				candidate_ns = workload(&fixtures[codec], payload, extent_size,
				    read_size, offsets, reads, 1, budget, minimum_work,
				    &peak_candidate, &candidate_hash);
				current_ns = workload(&fixtures[codec], payload, extent_size,
				    read_size, offsets, reads, 0, budget, minimum_work,
				    &peak_current, &current_hash);
			}
			if (sample != 0)
				printf(",");
			printf("%" PRIu64, current_ns);
			if (current_hash != candidate_hash || peak_candidate > budget)
				fail("sample hash or budget mismatch");
			/* Keep paired values for the second fixed-size arrays. */
			snprintf(path, sizeof(path), "%s/sample-%zu-%zu.tmp", argv[8], codec, sample);
			FILE *record = fopen(path, "w");
			if (record == NULL || fprintf(record, "%" PRIu64 " %zu %zu %" PRIu64 "\n",
			    candidate_ns, peak_current, peak_candidate, current_hash) < 0 || fclose(record) != 0)
				fail("sample record");
		}
		printf("],\"candidate_ns\":[");
		for (sample = 0; sample < samples; ++sample) {
			uint64_t saved_candidate, saved_hash;
			FILE *record;
			snprintf(path, sizeof(path), "%s/sample-%zu-%zu.tmp", argv[8], codec, sample);
			record = fopen(path, "r");
			if (record == NULL || fscanf(record, "%" SCNu64 " %zu %zu %" SCNu64,
			    &saved_candidate, &peak_current, &peak_candidate, &saved_hash) != 4 || fclose(record) != 0)
				fail("read sample record");
			if (sample != 0)
				printf(",");
			printf("%" PRIu64, saved_candidate);
		}
		printf("],\"current_peak_bytes\":[");
		for (sample = 0; sample < samples; ++sample) {
			uint64_t saved_candidate, saved_hash;
			FILE *record;
			snprintf(path, sizeof(path), "%s/sample-%zu-%zu.tmp", argv[8], codec, sample);
			record = fopen(path, "r");
			if (record == NULL || fscanf(record, "%" SCNu64 " %zu %zu %" SCNu64,
			    &saved_candidate, &peak_current, &peak_candidate, &saved_hash) != 4 || fclose(record) != 0)
				fail("read current peak");
			if (sample != 0)
				printf(",");
			printf("%zu", peak_current);
		}
		printf("],\"candidate_peak_bytes\":[");
		for (sample = 0; sample < samples; ++sample) {
			uint64_t saved_candidate, saved_hash;
			FILE *record;
			snprintf(path, sizeof(path), "%s/sample-%zu-%zu.tmp", argv[8], codec, sample);
			record = fopen(path, "r");
			if (record == NULL || fscanf(record, "%" SCNu64 " %zu %zu %" SCNu64,
			    &saved_candidate, &peak_current, &peak_candidate, &saved_hash) != 4 || fclose(record) != 0)
				fail("read candidate peak");
			if (sample != 0)
				printf(",");
			printf("%zu", peak_candidate);
			remove(path);
		}
		printf("]}");
	}
	eviction_oracle(&fixtures[CODEC_DEFLATE], &fixtures[CODEC_ZSTD], extent_size, argv[8]);
	printf("],\"status\":\"PASS\"}\n");
	for (codec = 0; codec < CODEC_COUNT; ++codec)
		free(fixtures[codec].compressed);
	free(offsets);
	free(verification);
	free(payload);
	return (0);
}
'''


def run_benchmark() -> tuple[dict[str, Any], dict[str, str]]:
    artifacts = OUTPUT / "artifacts"
    artifacts.mkdir()
    source = artifacts / "P15-038-codec-bench.c"
    source.write_text(BENCH_SOURCE.lstrip(), encoding="ascii")
    config = SPEC["benchmark"]
    budget = SPEC["budget"]
    with tempfile.TemporaryDirectory(prefix="p15-038-") as temporary:
        temp = Path(temporary)
        binary = temp / "P15-038-codec-bench"
        fixture_dir = temp / "fixtures"
        fixture_dir.mkdir()
        flags = run(
            ["pkg-config", "--cflags", "--libs", "liblz4", "liblzma", "zlib", "libzstd"],
            timeout=30,
        ).stdout.split()
        run(
            ["cc", "-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", str(source), "-o", str(binary), *flags],
            timeout=60,
            log=artifacts / "compile.log",
        )
        completed = run(
            [
                str(binary),
                str(config["extent_bytes"]),
                str(config["read_bytes"]),
                str(config["random_reads_per_sample"]),
                str(config["samples"]),
                str(budget["per_mount_bytes"]),
                str(budget["minimum_decode_work_bytes"]),
                str(config["seed"]),
                str(fixture_dir),
            ],
            timeout=240,
            log=artifacts / "benchmark.log",
            allowed={0, 10},
        )
        if completed.returncode != 0:
            raise GateFailure("STOP", "real codec benchmark or correctness oracle failed")
        try:
            report = json.loads(completed.stdout)
        except json.JSONDecodeError as error:
            raise GateFailure("RUNNER_FAIL", f"invalid benchmark JSON: {error}") from error
        hashes = {
            path.name: sha256_path(path)
            for path in sorted(fixture_dir.iterdir())
            if path.is_file()
        }
        before = hashes.pop("eviction-before.bin")
        after = hashes.pop("eviction-after.bin")
        if before != after:
            raise GateFailure("STOP", "real codec eviction changed decoded SHA-256")
        hashes["eviction-before-after.sha256"] = before
        write_json(artifacts / "fixture-hashes.json", hashes)
    return report, hashes


def evaluate(report: dict[str, Any], state: dict[str, Any]) -> dict[str, Any]:
    config = SPEC["benchmark"]
    budget = SPEC["budget"]["per_mount_bytes"]
    if report.get("status") != "PASS" or report.get("samples") != config["samples"]:
        raise GateFailure("STOP", "benchmark did not return exactly five complete samples")
    if report.get("reads_per_sample") != config["random_reads_per_sample"]:
        raise GateFailure("STOP", "benchmark logical workload changed")
    summaries = []
    improved = 0
    for codec in report.get("codecs", []):
        current = codec.get("current_ns", [])
        candidate = codec.get("candidate_ns", [])
        peaks = codec.get("candidate_peak_bytes", [])
        if len(current) != 5 or len(candidate) != 5 or len(peaks) != 5:
            raise GateFailure("STOP", f"{codec.get('codec')} has a missing sample")
        if any(value <= 0 for value in current + candidate):
            raise GateFailure("STOP", f"{codec['codec']} has a non-positive latency")
        if any(value > budget for value in peaks):
            raise GateFailure("STOP", f"{codec['codec']} exceeded the hard budget")
        current_median = statistics.median(current)
        candidate_median = statistics.median(candidate)
        improvement = (current_median - candidate_median) * 100.0 / current_median
        passed = improvement >= config["minimum_latency_improvement_percent"]
        if passed:
            improved += 1
        summaries.append(
            {
                "candidate_median_ns": candidate_median,
                "candidate_ns": candidate,
                "candidate_peak_bytes": peaks,
                "codec": codec["codec"],
                "compressed_bytes": codec["compressed_bytes"],
                "current_median_ns": current_median,
                "current_ns": current,
                "improvement_percent": improvement,
                "threshold_pass": passed,
            }
        )
    if len(summaries) != 4:
        raise GateFailure("STOP", "the four-codec benchmark is incomplete")
    if improved < config["minimum_improved_codecs"]:
        raise GateFailure("STOP", f"only {improved} codecs met the 10 percent latency threshold")
    if state.get("status") != "PASS" or state["budget"]["global_remaining"] != 0:
        raise GateFailure("STOP", "cache lifecycle state model did not close")
    return {
        "codecs_meeting_threshold": improved,
        "minimum_codecs": config["minimum_improved_codecs"],
        "minimum_improvement_percent": config["minimum_latency_improvement_percent"],
        "samples_included_per_variant": 5,
        "summaries": summaries,
    }


exit_code = 2
try:
    resolved, identity = verify_identity()
    write_json(OUTPUT / "identity.json", {"base": resolved, **identity})
    state = run_state_model()
    write_json(OUTPUT / "state-model.json", state)
    benchmark, fixture_hashes = run_benchmark()
    write_json(OUTPUT / "benchmark.json", benchmark)
    benefit = evaluate(benchmark, state)
    write_json(OUTPUT / "benefit.json", benefit)
    result = {
        "b33": "AUTHORIZED",
        "candidate": "P15-038",
        "decision": "GO",
        "eviction_sha256": fixture_hashes["eviction-before-after.sha256"],
        "full_feature_suite": "NOT_RUN",
        "gate": "G05",
        "qemu": "NOT_RUN",
        "reason": (
            f"state model PASS; {benefit['codecs_meeting_threshold']}/4 codecs meet "
            "the fixed five-sample >=10% host codec latency threshold; hard budgets and "
            "eviction hashes pass"
        ),
        "scope": SPEC["scope"],
        "source_modified": False,
        "status": "GO",
    }
    exit_code = 0
except GateFailure as error:
    result = {
        "b33": "STOP-NO-SOURCE" if error.status == "STOP" else "NOT_RUN",
        "candidate": "P15-038",
        "decision": "STOP" if error.status == "STOP" else error.status,
        "full_feature_suite": "NOT_RUN",
        "gate": "G05",
        "qemu": "NOT_RUN",
        "reason": error.reason,
        "scope": SPEC.get("scope"),
        "source_modified": False,
        "status": error.status,
    }
    exit_code = 1 if error.status == "STOP" else 2
except Exception as error:
    result = {
        "b33": "NOT_RUN",
        "candidate": "P15-038",
        "decision": "RUNNER_FAIL",
        "full_feature_suite": "NOT_RUN",
        "gate": "G05",
        "qemu": "NOT_RUN",
        "reason": f"unhandled gate error: {type(error).__name__}: {error}",
        "scope": SPEC.get("scope"),
        "source_modified": False,
        "status": "RUNNER_FAIL",
    }
    exit_code = 2
write_json(OUTPUT / "result.json", result)
write_json(
    OUTPUT / "cleanup.json",
    {
        "binary_remaining": False,
        "owned_processes_started": 0,
        "owned_qemu_started": False,
        "owned_temp_remaining": [],
        "protected_pid_touched": False,
        "protected_port_touched": False,
        "source_modified": False,
        "status": "PASS",
    },
)
lines = []
for path in sorted(OUTPUT.rglob("*")):
    if path.is_file() and path.name != "SHA256SUMS":
        lines.append(f"{sha256_path(path)}  {path.relative_to(OUTPUT)}")
(OUTPUT / "SHA256SUMS").write_text("\n".join(lines) + "\n", encoding="ascii")
print(json.dumps(result, sort_keys=True))
raise SystemExit(exit_code)
PY
