#!/usr/bin/env bash
set -euo pipefail

# test-pullemit-capture.sh — v0.8.0 [E] STEP E3 (club-3090 #141 / #147).
#
# Contract test for CONTRACT-4 (E3 half): the derived BOOT stage
# (HF_HOME host->container mount, NOT MODEL_DIR), the capability-aware
# DERIVED smoke prober (the conservative floor), and the 4 §6 capture-point
# artifact emitters + the §6.2/§6.3 manifest. The test IS the spec; the code
# is fixed to it. NO live Docker / GPU / network — an injected fixture
# runner + fixture client; artifacts written to a real tmp repo root and
# grepped on disk for redaction.
#
# Coverage:
#   * E3/E4-fix: booted_derived is a CONTEXT MANAGER — boot success yields
#     a live BootResult.ok handle WHILE the server is UP (smoke runs inside
#     the `with`, NOT after teardown); teardown (down) happens on __exit__
#     AFTER the with-body. Proves up -> (smoke, server alive) -> down, and
#     that teardown STILL runs if the with-body raises (on-rig E5 caught
#     the prior teardown-in-finally-before-smoke -> ConnectionRefused).
#   * boot container-died (fixture runner) -> ok=False handle STILL yielded
#     (CM does NOT raise for an expected boot failure), teardown STILL
#     invoked on __exit__ (always-teardown discipline at the CM scope).
#   * boot uses the HF_HOME host->container :ro mount carried in
#     compose_text (NOT MODEL_DIR); --project-directory discipline.
#   * smoke floor: plain-chat + streaming ALWAYS probed; a config declaring
#     NO tools -> tool-call:"unsmoked", partial:true; a config declaring
#     tools -> tool-call actually smoked.
#   * all 4 artifacts written with EXACT CONTRACT-4 schema/keys (pt1 gate /
#     pt2 download / pt3 boot / pt4 smoke), schema:1.
#   * manifest: §6.2 consensus-key fields FIRST-CLASS (asserted present at
#     top level, not only hashed) + submission_fingerprint present +
#     topology_summary_canonical present + §6.3 dedup inputs first-class
#     with failure_class == null (E3 does NOT classify).
#   * CAPTURE-POINT 5 NOT emitted (out of E3 scope).
#   * redaction asserted: no /opt/ai, no $HF_TOKEN value, no host/user in
#     ANY written artifact (grep the files on disk).

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"
export PYTHONPATH="$ROOT_DIR${PYTHONPATH:+:$PYTHONPATH}"

python3 - "$ROOT_DIR" <<'PY'
from __future__ import annotations

import json
import sys
import tempfile
from pathlib import Path
from types import SimpleNamespace

root = Path(sys.argv[1])
sys.path.insert(0, str(root))

from scripts.lib.profiles import booter as B          # noqa: E402
from scripts.lib.profiles import capture as C          # noqa: E402
from scripts.lib.profiles.downloader import DownloadResult  # noqa: E402
from scripts.lib.profiles.einput import EInput         # noqa: E402

failures: list[str] = []


def check(cond: bool, msg: str) -> None:
    if cond:
        print(f"PASS: {msg}")
    else:
        print(f"FAIL: {msg}", file=sys.stderr)
        failures.append(msg)


# ---------------------------------------------------------------------------
# A representative E1-emitted derived compose body. The load-bearing line for
# E3 is the HF_HOME host->container :ro volume (NOT MODEL_DIR).
# ---------------------------------------------------------------------------
COMPOSE_TEXT = (
    "# GENERATED by scripts/pull.sh — club-3090 [E] derived-vllm\n"
    "services:\n"
    "  vllm-derived-org-my-model:\n"
    "    image: vllm/vllm-openai:nightly-deadbeef\n"
    "    volumes:\n"
    "      - /data/hf/club3090/pulls/org-my-model:"
    "/models/club3090/pulls/org-my-model:ro\n"
    "    environment:\n"
    "      - NVIDIA_VISIBLE_DEVICES=0\n"
    "      - HUGGING_FACE_HUB_TOKEN=${HF_TOKEN:-}\n"
)


def mk_einput(slug, *, der, terminal="proceed"):
    return EInput(
        slug=slug, terminal=terminal, is_override_accepted=False, der=der,
        runtime={"engine": "vllm-stable", "kv_format": "bf16",
                 "tp": 1, "max_ctx": 131072, "default_port": 8071},
        selected_files=[], hf_home=Path("/data/hf"), c2a=None,
        hardware_sm=8.6, visible_gpu_count=1, per_gpu_vram_mib=[24576],
        selected_gpu_indices=[0], selected_gpu_vram_mib=[24576],
        # Kimi-r1 L2 / F6: synced to the LIVE detect_gpu_topology format —
        # `nvidia-smi --query-gpu=...,name` returns the FULL GPU name, so
        # _topology_summary_canonical serializes `(NVIDIA GeForce RTX 3090,
        # 24576)`. The real on-disk E5 captures carry this exact string.
        topology_summary="[(NVIDIA GeForce RTX 3090, 24576)]",
        club3090_commit="deadbeef",
        diagnostics={},
    )


# ---------------------------------------------------------------------------
# Fixture runner: records up/wait_ready/down (and test-injected "smoke")
# calls in invocation order; can simulate a dead container by raising
# BootError from up() or wait_ready(). With booted_derived (a CM), down()
# is only invoked on __exit__ — AFTER the `with` body — so the recorded
# order proves up -> (smoke while UP) -> down.
# ---------------------------------------------------------------------------
class FixtureRunner:
    def __init__(self, *, die_on="", reason=""):
        self.die_on = die_on
        self.reason = reason
        self.calls: list[tuple[str, str, str]] = []

    def up(self, project_dir, compose_path):
        self.calls.append(("up", project_dir, compose_path))
        # Assert the proven HF_HOME mount is present in the written compose
        # (NOT MODEL_DIR) and the project dir is a real isolated dir.
        body = Path(compose_path).read_text()
        assert "/models/club3090/pulls/" in body and ":ro" in body, \
            "compose missing HF_HOME host->container :ro mount"
        assert "MODEL_DIR" not in body, "derived boot must NOT use MODEL_DIR"
        assert Path(project_dir).is_dir(), "project dir must exist for --project-directory"
        if self.die_on == "up":
            raise B.BootError(self.reason or "container exited (code 1)")

    def wait_ready(self, endpoint):
        self.calls.append(("wait_ready", endpoint, ""))
        if self.die_on == "wait_ready":
            raise B.BootError(self.reason or "container died before ready")

    def down(self, project_dir, compose_path):
        self.calls.append(("down", project_dir, compose_path))


# ---------------------------------------------------------------------------
# 1. E3/E4-fix — booted_derived is a CONTEXT MANAGER. Boot success ->
#    BootResult.ok handle yielded WHILE the server is UP; smoke runs INSIDE
#    the `with` (server alive, NOT torn down); teardown (down) happens on
#    __exit__, AFTER the with-body. This is the assertion that proves the
#    on-rig E5 teardown-in-finally-before-smoke -> ConnectionRefused
#    root-cause is fixed (prior boot_derived tore down before returning).
# ---------------------------------------------------------------------------
der_plain = SimpleNamespace(profile={"arch": "LlamaForCausalLM",
                                      "weight_format": "bfloat16"})
ei = mk_einput("org/My-Model", der=der_plain)
r = FixtureRunner()
with B.booted_derived(ei, COMPOSE_TEXT, runner=r) as res:
    # INSIDE the with-body: server is UP. down() must NOT have run yet.
    check(res.ok is True and res.failure is None,
          f"boot success: BootResult.ok handle yielded "
          f"(got ok={res.ok} fail={res.failure})")
    check(res.endpoint == "http://127.0.0.1:8071/v1",
          f"boot success: endpoint on host-published port "
          f"(got {res.endpoint})")
    _evs = [c[0] for c in r.calls]
    check("up" in _evs and "wait_ready" in _evs,
          "boot success: up + wait_ready invoked before the handle is "
          "yielded")
    check("down" not in _evs,
          "E3/E4-fix: teardown (down) has NOT run yet INSIDE the `with` — "
          "the server is ALIVE for smoke+capture (the on-rig E5 fix)")
    # Simulate the orchestrator smoking the LIVE server inside the body.
    r.calls.append(("smoke", res.endpoint, ""))
# <- CM __exit__ HERE.
_ev = [c[0] for c in r.calls]
check(_ev[-1] == "down",
      f"boot success: teardown (down) ALWAYS invoked LAST, on __exit__, "
      f"AFTER the with-body smoke (calls={_ev})")
check(_ev.index("down") > _ev.index("smoke") > _ev.index("up"),
      f"boot success: order is up -> ... -> smoke -> down (teardown idx > "
      f"smoke idx > up idx) (calls={_ev})")
# Snapshot the (now torn-down) handle for the §4 capture test reuse — the
# capture test only reads .ok/.seconds/.failure (a value object, valid
# post-exit).
res_snapshot = B.BootResult(ok=res.ok, seconds=res.seconds,
                            failure=res.failure, endpoint=res.endpoint)
res = res_snapshot

# ---------------------------------------------------------------------------
# 2. Boot container-died -> ok=False handle STILL yielded (CM does NOT raise
#    for an EXPECTED boot failure — orchestrator must still emit pt3 +
#    capture) + teardown STILL invoked on __exit__.
# ---------------------------------------------------------------------------
r2 = FixtureRunner(die_on="wait_ready", reason="CUDA OOM; worker exited")
with B.booted_derived(ei, COMPOSE_TEXT, runner=r2) as res2:
    check(res2.ok is False and res2.failure == "CUDA OOM; worker exited",
          f"boot died: ok=False handle YIELDED (not raised) + failure "
          f"populated (got ok={res2.ok} fail={res2.failure!r})")
    check(res2.endpoint is None, "boot died: endpoint None on failure")
    check("down" not in [c[0] for c in r2.calls],
          "boot died: teardown deferred to __exit__ (still inside `with`)")
check("down" in [c[0] for c in r2.calls]
      and [c[0] for c in r2.calls][-1] == "down",
      "boot died: teardown STILL invoked on __exit__ (always-teardown "
      "discipline preserved at the CM scope)")

r3 = FixtureRunner(die_on="up", reason="image pull failed")
with B.booted_derived(ei, COMPOSE_TEXT, runner=r3) as res3:
    check(res3.ok is False and "image pull failed" in (res3.failure or ""),
          f"boot up-fail: ok=False handle yielded, failure surfaced "
          f"(got {res3.failure!r})")
check("down" in [c[0] for c in r3.calls],
      "boot up-fail: teardown STILL invoked on __exit__")

# 2b. E3/E4-fix — teardown STILL runs if the `with` BODY raises (the CM's
#     finally fires on the exceptional __exit__; no orphan ever).
r4 = FixtureRunner()
_raised = False
try:
    with B.booted_derived(ei, COMPOSE_TEXT, runner=r4) as res4:
        check(res4.ok is True, "boot exc-body: server up before body raises")
        check("down" not in [c[0] for c in r4.calls],
              "boot exc-body: down not run yet (inside body)")
        raise RuntimeError("orchestrator-body blew up mid-with")
except RuntimeError:
    _raised = True
check(_raised, "boot exc-body: the with-body exception still propagates")
check([c[0] for c in r4.calls][-1] == "down",
      "boot exc-body: CM teardown STILL ran on the exceptional __exit__ "
      "(no-orphan guarantee preserved even when the body raises)")

# ---------------------------------------------------------------------------
# 3. Smoke floor: plain-chat + streaming ALWAYS; no-tools -> unsmoked/partial.
# ---------------------------------------------------------------------------
class FixtureClient:
    # Lock-step with the E3-fix probe signature: smoke_derived now passes
    # the resolved served-model-name as a 3rd arg (NOT the literal
    # "derived"). The fixture records it so the test can assert it.
    def __init__(self, green=True):
        self.green = green
        self.probed: list[str] = []
        self.models: list[str] = []

    def probe(self, capability, endpoint, model_name):
        self.probed.append(capability)
        self.models.append(model_name)
        return self.green


# config declares NO optional capabilities -> floor only.
fc = FixtureClient(green=True)
sm = C.smoke_derived(ei, "http://127.0.0.1:8071/v1", client=fc)
check(set(fc.probed) == {"plain-chat", "streaming"},
      f"smoke floor: ONLY plain-chat+streaming probed when nothing declared "
      f"(probed={sorted(fc.probed)})")
check(sm.results["plain-chat"] == "green"
      and sm.results["streaming"] == "green",
      "smoke floor: plain-chat + streaming green")
check(sm.results["tool-call"] == "unsmoked",
      "smoke floor: undeclared tool-call -> 'unsmoked'")
check(all(sm.results[c] == "unsmoked" for c in
          ("tool-call", "reasoning-streaming", "structured-output",
           "vision", "long-context")),
      "smoke floor: every undeclared optional cap -> 'unsmoked'")
check(sm.partial is True,
      "smoke floor: partial:true when any capability un-smoked (§6.2)")
check(sm.smoke_capability_set == ["plain-chat", "streaming"],
      f"smoke floor: smoke_capability_set == floor (got {sm.smoke_capability_set})")

# config POSITIVELY declares tool-call -> tool-call actually smoked.
der_tools = SimpleNamespace(profile={
    "arch": "LlamaForCausalLM", "weight_format": "bfloat16",
    "config": {"tool_use": True},
})
ei_t = mk_einput("org/Tooly", der=der_tools)
fc2 = FixtureClient(green=True)
sm2 = C.smoke_derived(ei_t, "http://x/v1", client=fc2)
check("tool-call" in fc2.probed,
      "smoke declared: tool-call IS probed when config declares it")
check(sm2.results["tool-call"] == "green",
      "smoke declared: declared tool-call -> green (probed, not unsmoked)")
check("tool-call" in sm2.smoke_capability_set,
      "smoke declared: tool-call in smoke_capability_set")
check(sm2.partial is True,
      "smoke declared: still partial (vision/etc undeclared -> unsmoked)")

# red probe -> recorded red (not unsmoked).
fc3 = FixtureClient(green=False)
sm3 = C.smoke_derived(ei, "http://x/v1", client=fc3)
check(sm3.results["plain-chat"] == "red" and sm3.results["streaming"] == "red",
      "smoke red: a failing floor probe records 'red' (caught #145 class)")

# ---------------------------------------------------------------------------
# 3b. E3-fix: the probe MUST send the real served-model-name (NOT the literal
#     "derived") + a non-200 produces red WITH the additive status/error
#     detail. This is the on-rig E5 defect: healthy boot, model:"derived" is
#     an unknown served name -> vLLM 404 -> every floor probe red.
# ---------------------------------------------------------------------------
from scripts.lib.profiles.downloader import sanitize_slug as _san  # noqa: E402

# (i) smoke_derived passes the resolved served-model-name to the client.
fc_name = FixtureClient(green=True)
C.smoke_derived(ei, "http://127.0.0.1:8071/v1", client=fc_name)
expected_name = _san(ei.slug)  # == generate_from_profile --served-model-name
check(expected_name == "org-my-model",
      f"E3-fix: sanitize_slug(slug) == emitted served-model-name "
      f"(got {expected_name!r})")
check(set(fc_name.models) == {expected_name},
      f"E3-fix: probe receives the REAL served-model-name, not 'derived' "
      f"(got {sorted(set(fc_name.models))})")
check("derived" not in fc_name.models,
      "E3-fix: the literal 'derived' is NEVER sent as the model field")

# (ii) compose_meta.served_model_name (priority a) overrides the slug fallback.
fc_meta = FixtureClient(green=True)
C.smoke_derived(ei, "http://x/v1", client=fc_meta,
                compose_meta={"served_model_name": "qwen-qwen2.5-0.5b-instruct"})
check(set(fc_meta.models) == {"qwen-qwen2.5-0.5b-instruct"},
      f"E3-fix: compose_meta.served_model_name is authoritative (priority a) "
      f"(got {sorted(set(fc_meta.models))})")

# (iii) the REAL _HttpSmokeClient builds the body with the real model name
#       (request-building unit; NO network — _build_body is factored out).
real = C._HttpSmokeClient()
b_plain = real._build_body("plain-chat", expected_name)
check(b_plain["model"] == expected_name and b_plain["model"] != "derived",
      f"E3-fix: real client body model=='{expected_name}' not 'derived' "
      f"(got {b_plain['model']!r})")
check(b_plain.get("stream") is None,
      "E3-fix: plain-chat body has no stream flag")
b_str = real._build_body("streaming", expected_name)
check(b_str["model"] == expected_name and b_str.get("stream") is True,
      "E3-fix: streaming body keeps stream:true + real model name")

# (iv) a simulated non-200 -> red WITH the additive status/error detail.
class Fixture404Client:
    def probe(self, capability, endpoint, model_name):
        # mirrors _HttpSmokeClient's (ok, status, error) contract on a 404.
        return (False, 404,
                f'HTTP 404: {{"object":"error","message":'
                f'"The model `{model_name}` does not exist."}}')

sm404 = C.smoke_derived(ei, "http://x/v1", client=Fixture404Client())
check(sm404.results["plain-chat"] == "red"
      and sm404.results["streaming"] == "red",
      "E3-fix: non-200 floor probe -> red (the on-rig red-smoke symptom)")
check(sm404.results_detail.get("plain-chat", {}).get("status") == 404
      and "does not exist" in
          sm404.results_detail.get("plain-chat", {}).get("error", ""),
      f"E3-fix: red carries additive {{status:404, error:...}} detail "
      f"(got {sm404.results_detail.get('plain-chat')!r})")
check("streaming" in sm404.results_detail
      and sm404.results_detail["streaming"]["status"] == 404,
      "E3-fix: every failing capability gets a results_detail entry")
# green caps carry NO detail (additive only on red).
sm_green = C.smoke_derived(ei, "http://x/v1", client=FixtureClient(green=True))
check(sm_green.results_detail == {},
      "E3-fix: results_detail empty when nothing is red (additive-on-red)")

# (v) legacy 2-arg fixture client still works (injected-seam compatibility).
class LegacyClient:
    def __init__(self):
        self.calls = 0
    def probe(self, capability, endpoint):  # OLD signature
        self.calls += 1
        return True

lc = LegacyClient()
sm_legacy = C.smoke_derived(ei, "http://x/v1", client=lc)
check(lc.calls >= 2 and sm_legacy.results["plain-chat"] == "green",
      "E3-fix: legacy 2-arg client still accepted (seam not weakened)")

# ---------------------------------------------------------------------------
# 4. The 4 artifacts + manifest: EXACT CONTRACT-4 schema/keys, redacted.
# ---------------------------------------------------------------------------
dl = DownloadResult(ok=True, files=["model.safetensors", "config.json"],
                    bytes=11_000_000_000, sha_verified=True, failure=None,
                    local_dir="/data/hf/club3090/pulls/org-my-model")
compose_meta = {
    "resolved_image": "vllm/vllm-openai:nightly-deadbeef",
    "max_model_len": 131072, "kv_format": "bf16",
    "engine": "vllm-stable",
}

with tempfile.TemporaryDirectory() as td:
    out = C.emit_capture(
        ei,
        confidence=SimpleNamespace(name="ESTIMATED_LOWER_BOUND"),
        raw_verdict="fits-clean",
        profile_like="vllm/gemma-26ba4b-single",
        download_result=dl,
        boot_result=res,
        smoke_result=sm,
        compose_meta=compose_meta,
        kv_calc_version="kvcalc-v7",
        repo_root=Path(td),
        ts="20260517T120000Z",
    )
    p = out["paths"]

    pt1 = json.loads(Path(p["gate"]).read_text())
    # F3/G6-A-i: pt1 gains the ALWAYS-present additive
    # `predicted_b_breakdown` key (None when the caller passes nothing —
    # this call site does not). EXISTING keys byte-unchanged; the ONLY
    # delta is the new key (tightened, never weakened).
    check(pt1 == {
        "schema": 1, "point": "gate", "slug": "org/My-Model",
        "confidence": "ESTIMATED_LOWER_BOUND", "raw_verdict": "fits-clean",
        "terminal": "proceed", "profile_like": "vllm/gemma-26ba4b-single",
        "hardware_sm": 8.6, "predicted_b_breakdown": None,
    }, f"pt1 gate EXACT CONTRACT-4 schema/keys + additive "
       f"predicted_b_breakdown (got {sorted(pt1)})")
    check(set(pt1) - {"predicted_b_breakdown"} == {
        "schema", "point", "slug", "confidence", "raw_verdict",
        "terminal", "profile_like", "hardware_sm"},
        "F3/G6-A-i: predicted_b_breakdown is the ONLY new pt1 key "
        "(every locked pt1 key byte-preserved)")

    pt2 = json.loads(Path(p["download"]).read_text())
    check(pt2 == {
        "point": "download", "ok": True,
        "files": ["model.safetensors", "config.json"],
        "bytes": 11_000_000_000, "sha_verified": True, "failure": None,
    }, f"pt2 download EXACT CONTRACT-4 (E2 DownloadResult shape) "
       f"(got {sorted(pt2)})")

    pt3 = json.loads(Path(p["boot"]).read_text())
    check(set(pt3) == {"point", "ok", "seconds", "failure"}
          and pt3["point"] == "boot" and pt3["ok"] is True
          and pt3["failure"] is None,
          f"pt3 boot EXACT CONTRACT-4 (got {sorted(pt3)})")

    pt4 = json.loads(Path(p["smoke"]).read_text())
    # LOCKED CONTRACT-4 keys unchanged; `results_detail` is the ONLY
    # additive field (E3-fix). Assert the locked shape/values explicitly +
    # that the additive key is present (tightened, never weakened).
    check(set(pt4) == {"point", "smoke_capability_set", "results",
                       "partial", "results_detail"}
          and pt4["point"] == "smoke"
          and pt4["smoke_capability_set"] == ["plain-chat", "streaming"]
          and pt4["partial"] is True
          and pt4["results"]["tool-call"] == "unsmoked",
          f"pt4 smoke CONTRACT-4 locked keys + additive results_detail "
          f"(got {sorted(pt4)})")
    check(pt4["results"]["plain-chat"] == "green"
          and pt4["results"]["streaming"] == "green",
          "pt4 smoke: locked `results` byte-behaviour-unchanged (green floor)")
    check(isinstance(pt4["results_detail"], dict),
          "pt4 smoke: results_detail is an (additive) dict")

    # CAPTURE-POINT 5 NOT emitted (out of E3 scope).
    cap_dir = Path(out["dir"])
    files = sorted(x.name for x in cap_dir.iterdir())
    check(files == ["manifest.json", "pt1-gate.json", "pt2-download.json",
                    "pt3-boot.json", "pt4-smoke.json"],
          f"only pt1-4 + manifest written; NO pt5/override_capture "
          f"(got {files})")
    check(not any("override" in f for f in files),
          "CAPTURE-POINT 5 (override-accepted force-capture) NOT emitted (E4)")

    # ---- manifest: consensus-key fields FIRST-CLASS + failure_class null --
    man = json.loads(Path(p["manifest"]).read_text())
    for k in ("model", "quant_label", "arch_family", "topology_class",
              "engine_pin", "engine_version", "kv_calc_version",
              "selected_ctx", "kv_format", "smoke_capability_set",
              "topology_summary_canonical", "submission_fingerprint",
              "model_id", "failure_class"):
        check(k in man,
              f"manifest: §6.2/§6.3 key {k!r} is FIRST-CLASS (top level, "
              f"not only hashed)")
    check(man["model"] == "org/My-Model"
          and man["quant_label"] == "bfloat16"
          and man["arch_family"] == "LlamaForCausalLM"
          and man["engine_pin"] == "vllm/vllm-openai:nightly-deadbeef"
          and man["kv_calc_version"] == "kvcalc-v7"
          and man["selected_ctx"] == 131072
          and man["kv_format"] == "bf16"
          and man["smoke_capability_set"] == ["plain-chat", "streaming"]
          and man["topology_summary_canonical"]
              == "[(NVIDIA GeForce RTX 3090, 24576)]",
          "manifest: consensus-key values populated from einput/compose_meta")
    check(man["failure_class"] is None,
          "manifest: failure_class is NULL — E3 must NOT classify "
          "(§6.1 = [F]'s job)")
    check(isinstance(man["submission_fingerprint"], str)
          and len(man["submission_fingerprint"]) == 64,
          "manifest: submission_fingerprint is the §6.2 H(...) hash")

    # ---- redaction: NO path/token/host/user leak in ANY artifact --------
    import os
    import subprocess
    user = os.environ.get("USER") or ""
    host = subprocess.run(["hostname", "-s"], capture_output=True,
                          text=True).stdout.strip()
    for art in p.values():
        blob = Path(art).read_text()
        check("/opt/ai" not in blob,
              f"redaction: no /opt/ai path leak in {Path(art).name}")
        check("HF_TOKEN=" not in blob or "HF_TOKEN=<REDACTED>" in blob
              or "HF_TOKEN=" not in blob,
              f"redaction: no raw HF_TOKEN value in {Path(art).name}")
        check("hf_" not in blob or "hf_<REDACTED>" in blob
              or not any(seg.startswith("hf_") and len(seg) > 33
                         for seg in blob.split()),
              f"redaction: no hf_ token in {Path(art).name}")
        if user and user not in ("root",):
            check(f"/home/{user}" not in blob,
                  f"redaction: no /home/{user} in {Path(art).name}")
        if host:
            check(host not in blob or host == "<HOST>",
                  f"redaction: no bare hostname in {Path(art).name}")

    # A redaction smoke: an einput carrying a leaky path/token gets scrubbed.
    leaky = mk_einput("org/Leaky",
                      der=SimpleNamespace(profile={
                          "arch": "/opt/ai/secret",
                          "weight_format": "bfloat16"}))
    out2 = C.emit_capture(
        leaky, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="fits-clean", profile_like="vllm/x",
        download_result=dl, boot_result=res, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T130000Z",
    )
    for art in out2["paths"].values():
        check("/opt/ai" not in Path(art).read_text(),
              f"redaction: leaky /opt/ai scrubbed from {Path(art).name}")

# ---------------------------------------------------------------------------
# 4b. F3/G6-A — the 3-part additive [E] touch (CONTRACT-2 G6-(A)):
#   A-i  pt1.predicted_b_breakdown CARRIES the [B] breakdown when passed
#        (was disk-only in pt5; now on every post-[B] capture);
#   A-ii pt3.failure_log_excerpt is written ONLY when boot NOT ok (success
#        path byte-identical — proven by §4's boot-ok pt3 exact-shape);
#   A-ii′ pt3.actual.{attempted_alloc_mib,gpu_worker_reported_mib} is parsed
#        AT CAPTURE TIME by [E] from that SAME excerpt (classifier only
#        reads it). All additive; existing keys byte-preserved.
# ---------------------------------------------------------------------------
PRED_BREAKDOWN = {"weights_gb": 9.0, "kv_gb": 12.0, "overhead_gb": 1.5}
# A representative redacted-able container log: vLLM OOM traceback ("Tried
# to allocate ...") + the gpu_worker measured-peak line + a leaky path the
# SAME _redact_text discipline must scrub.
OOM_LOG = (
    "vllm-derived-1  | INFO gpu_worker.py:184 GPU memory peak: "
    "22880.00 MiB after profiling\n"
    "vllm-derived-1  | Loading weights from /opt/ai/secret/model ...\n"
    "vllm-derived-1  | torch.cuda.OutOfMemoryError: CUDA out of memory. "
    "Tried to allocate 2.50 GiB (GPU 0; 24.00 GiB total capacity)\n"
)
res_boot_fail = B.BootResult(
    ok=False, seconds=4.2,
    failure="server did not become ready before timeout",
    endpoint=None,
)

with tempfile.TemporaryDirectory() as td:
    # (A-i) pt1 carries predicted_b_breakdown VERBATIM when passed.
    out_ok = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="fits-clean", profile_like="vllm/x",
        download_result=dl, boot_result=res, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T150000Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
    )
    pt1_b = json.loads(Path(out_ok["paths"]["gate"]).read_text())
    check(pt1_b["predicted_b_breakdown"] == PRED_BREAKDOWN,
          f"F3 A-i: pt1.predicted_b_breakdown carries the [B] breakdown "
          f"when passed (got {pt1_b.get('predicted_b_breakdown')!r})")
    # boot-OK -> pt3 stays the locked 4-key shape (A-ii: only-when-not-ok).
    pt3_ok = json.loads(Path(out_ok["paths"]["boot"]).read_text())
    check(set(pt3_ok) == {"point", "ok", "seconds", "failure"},
          f"F3 A-ii: boot-OK pt3 byte-identical (NO failure_log_excerpt / "
          f"actual on the success path) (got {sorted(pt3_ok)})")

    # (A-ii + A-ii′) boot-FAIL with an explicit failure_log_excerpt ->
    # pt3 gains failure_log_excerpt (redacted) + the parsed actual object.
    out_f = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T151000Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
        failure_log_excerpt=OOM_LOG,
    )
    pt3_f = json.loads(Path(out_f["paths"]["boot"]).read_text())
    check(pt3_f["point"] == "boot" and pt3_f["ok"] is False,
          "F3 A-ii: failure pt3 keeps the locked point/ok/seconds/failure")
    check("failure_log_excerpt" in pt3_f and pt3_f["failure_log_excerpt"],
          "F3 A-ii: pt3.failure_log_excerpt present on boot failure")
    check("Tried to allocate 2.50 GiB" in pt3_f["failure_log_excerpt"]
          and "/opt/ai" not in pt3_f["failure_log_excerpt"],
          "F3 A-ii: excerpt carries the OOM traceback + is _redact_text'd "
          "(no /opt/ai leak)")
    check(isinstance(pt3_f.get("actual"), dict)
          and set(pt3_f["actual"]) == {"attempted_alloc_mib",
                                       "gpu_worker_reported_mib"},
          f"F3 A-ii′: pt3.actual structured object emitted at capture time "
          f"(got {pt3_f.get('actual')!r})")
    check(pt3_f["actual"]["attempted_alloc_mib"] == 2560,
          f"F3 A-ii′: attempted_alloc_mib parsed (2.50 GiB -> 2560 MiB) "
          f"(got {pt3_f['actual']['attempted_alloc_mib']})")
    check(pt3_f["actual"]["gpu_worker_reported_mib"] == 22880,
          f"F3 A-ii′: gpu_worker_reported_mib parsed from the gpu_worker "
          f"peak line (22880 MiB) (got "
          f"{pt3_f['actual']['gpu_worker_reported_mib']})")

    # (A-ii′ honest-degrade) boot-FAIL with NO excerpt -> keys still added
    # (only-when-not-ok) but actual is all-None (never fabricated).
    out_n = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T152000Z",
    )
    pt3_n = json.loads(Path(out_n["paths"]["boot"]).read_text())
    check(pt3_n.get("failure_log_excerpt") is None
          and pt3_n["actual"] == {"attempted_alloc_mib": None,
                                  "gpu_worker_reported_mib": None},
          f"F3 A-ii′ honest-degrade: no excerpt -> actual all-None, never "
          f"fabricated (got {pt3_n.get('actual')!r})")

    # ── F8-fix: real modern vLLM v0.21.0+ KV-cache-too-large phrasing ──
    # The on-rig F8 validator induced a GENUINE vLLM KV-cache-too-large
    # failure. vLLM nightly bf610c2f (v0.21.0+ memory-profiler regime)
    # raises a CLEAN ValueError from _check_enough_kv_cache_memory — NOT
    # torch.cuda.OutOfMemoryError — so the CLASSIC "Tried to allocate" /
    # "peak memory" regexes match NEITHER line and the real common failure
    # was silently parsed {None,None} (Tier-1 never routed). These lines
    # are copied VERBATIM from a captured real vLLM KV-OOM log (the
    # gpu_worker-available line + the ValueError signature).
    F8_REAL_OOM_LOG = (
        "f8d-oom-vllm  | (EngineCore pid=81) INFO 05-17 05:13:55 "
        "[gpu_worker.py:462] Available KV cache memory: 20.89 GiB\n"
        "f8d-oom-vllm  | (EngineCore pid=81) ERROR 05-17 05:13:55 "
        "[core.py:1159] ValueError: To serve at least one request with the "
        "models's max seq len (2000000), (22.89 GiB KV cache is needed, "
        "which is larger than the available KV cache memory (20.89 GiB). "
        "Based on the available memory, the estimated maximum model length "
        "is 1825488. Try increasing `gpu_memory_utilization` or decreasing "
        "`max_model_len` when initializing the engine.\n"
    )
    # 22.89 GiB -> round(22.89*1024) = 23439 MiB ; 20.89 GiB ->
    # round(20.89*1024) = 21391 MiB. Allow ±2 MiB for GiB->MiB rounding.
    _F8_TOL = 2
    out_f8 = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T154500Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
        failure_log_excerpt=F8_REAL_OOM_LOG,
    )
    pt3_f8 = json.loads(Path(out_f8["paths"]["boot"]).read_text())
    _aa = pt3_f8["actual"]["attempted_alloc_mib"]
    _gw = pt3_f8["actual"]["gpu_worker_reported_mib"]
    check(_aa is not None and abs(_aa - 23439) <= _F8_TOL,
          f"F8-fix: real vLLM '(22.89 GiB KV cache is needed' -> "
          f"attempted_alloc_mib ~= 23439 (got {_aa})")
    check(_gw is not None and abs(_gw - 21391) <= _F8_TOL,
          f"F8-fix: real vLLM gpu_worker 'Available KV cache memory: "
          f"20.89 GiB' -> gpu_worker_reported_mib ~= 21391 (got {_gw})")

    # F8-fix: the gpu_worker.py explicit line is PREFERRED, but the
    # ValueError parenthetical "available KV cache memory (20.89 GiB)"
    # alone must ALSO populate gpu_worker_reported_mib (the fallback path).
    F8_VALUEERROR_ONLY = (
        "f8d-oom-vllm  | (EngineCore pid=81) ERROR 05-17 05:13:55 "
        "[core.py:1159] ValueError: To serve at least one request with the "
        "models's max seq len (2000000), (22.89 GiB KV cache is needed, "
        "which is larger than the available KV cache memory (20.89 GiB).\n"
    )
    out_f8b = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T154600Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
        failure_log_excerpt=F8_VALUEERROR_ONLY,
    )
    pt3_f8b = json.loads(Path(out_f8b["paths"]["boot"]).read_text())
    _aa2 = pt3_f8b["actual"]["attempted_alloc_mib"]
    _gw2 = pt3_f8b["actual"]["gpu_worker_reported_mib"]
    check(_aa2 is not None and abs(_aa2 - 23439) <= _F8_TOL,
          f"F8-fix: ValueError-only excerpt -> attempted ~= 23439 "
          f"(got {_aa2})")
    check(_gw2 is not None and abs(_gw2 - 21391) <= _F8_TOL,
          f"F8-fix: ValueError parenthetical fallback -> "
          f"gpu_worker_reported_mib ~= 21391 (got {_gw2})")

    # F8-fix CLASSIC NO-REGRESSION: an excerpt with the CLASSIC
    # torch.cuda.OutOfMemoryError "Tried to allocate 2.50 GiB" + a classic
    # "peak memory ... 22880 MiB" line STILL parses BOTH fields exactly as
    # before (the new vLLM-KV regexes must never shadow the classic path).
    CLASSIC_OOM_LOG = (
        "vllm-derived-1  | INFO gpu_worker.py:184 peak memory: "
        "22880 MiB after profiling\n"
        "vllm-derived-1  | torch.cuda.OutOfMemoryError: CUDA out of "
        "memory. Tried to allocate 2.50 GiB (GPU 0; 24.00 GiB total)\n"
    )
    out_cl = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T154700Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
        failure_log_excerpt=CLASSIC_OOM_LOG,
    )
    pt3_cl = json.loads(Path(out_cl["paths"]["boot"]).read_text())
    check(pt3_cl["actual"]["attempted_alloc_mib"] == 2560
          and pt3_cl["actual"]["gpu_worker_reported_mib"] == 22880,
          f"F8-fix NO-REGRESSION: classic torch OOM 'Tried to allocate "
          f"2.50 GiB' (2560) + 'peak memory 22880 MiB' STILL both parse "
          f"(got {pt3_cl['actual']!r})")

    # F8-fix honest-degrade preserved: a non-OOM excerpt with NEITHER
    # phrasing -> actual all-None (never fabricated).
    out_hd = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_boot_fail, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T154800Z",
        predicted_b_breakdown=PRED_BREAKDOWN,
        failure_log_excerpt="some unrelated boot error, no oom at all\n",
    )
    pt3_hd = json.loads(Path(out_hd["paths"]["boot"]).read_text())
    check(pt3_hd["actual"] == {"attempted_alloc_mib": None,
                               "gpu_worker_reported_mib": None},
          f"F8-fix: non-OOM excerpt -> actual all-None honest-degrade "
          f"preserved (got {pt3_hd['actual']!r})")

    # The booter stashes failure_log_excerpt on the BootResult; emit_capture
    # picks it up WITHOUT an explicit param (the pull.py pass-through path).
    res_with_log = B.BootResult(
        ok=False, seconds=1.0, failure="boom", endpoint=None,
        failure_log_excerpt=OOM_LOG,
    )
    out_s = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="wont-fit", profile_like="vllm/x",
        download_result=dl, boot_result=res_with_log, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=Path(td), ts="20260517T153000Z",
    )
    pt3_s = json.loads(Path(out_s["paths"]["boot"]).read_text())
    check(pt3_s["actual"]["attempted_alloc_mib"] == 2560
          and pt3_s["actual"]["gpu_worker_reported_mib"] == 22880,
          "F3: BootResult.failure_log_excerpt is read by emit_capture even "
          "without an explicit param (the booter->pull->[E] path)")

    # Default-call (NO new params) — pt1 carries the key as None; pt3 on a
    # boot-OK capture has NO new keys at all (forward-compat: F1
    # read_capture_bundle + F2 classify already tolerate this exactly).
    from scripts.lib.profiles.loop_input import read_capture_bundle as _rcb
    fi_ok = _rcb(Path(out_ok["dir"]))
    check(fi_ok.pt1_gate["predicted_b_breakdown"] == PRED_BREAKDOWN,
          "F3 forward-compat: F1 read_capture_bundle surfaces "
          "pt1.predicted_b_breakdown")
    check("failure_log_excerpt" not in fi_ok.pt3_boot
          and "actual" not in fi_ok.pt3_boot,
          "F3 forward-compat: boot-OK bundle parses with NO pt3 F3 keys "
          "(additive-absent tolerated)")

# ---------------------------------------------------------------------------
# 5. E-outcome-fix: honest 3-state manifest `outcome` (failed>partial>ok),
#    derived PURELY from structured truth in scope (download.ok, boot.ok,
#    smoke.results, smoke.partial). The §6.2 invariant under test: an anchor
#    with floor-green + optionals-unsmoked is a CAPABILITY-SCOPED SUCCESS
#    (`outcome=="partial"`) and is explicitly NOT bucketed as a failure
#    (the old buggy 2-way derivation emitted the literal "partial-or-failed",
#    conflating partial-success with red — a §1 confidently-wrong defect).
#    `outcome` remains an input to submission_fingerprint exactly as before;
#    only its VALUE becomes honest. No new fields; no re-derivation.
# ---------------------------------------------------------------------------
SmokeResult = C.SmokeResult
dl_ok = DownloadResult(ok=True, files=["model.safetensors"],
                       bytes=1, sha_verified=True, failure=None,
                       local_dir="/data/hf/club3090/pulls/org-q")
res_ok = B.BootResult(ok=True, seconds=1.0, failure=None,
                      endpoint="http://127.0.0.1:8071/v1")
res_bad = B.BootResult(ok=False, seconds=0.0, failure="CUDA OOM",
                       endpoint=None)
cm_ok = {"resolved_image": "vllm/vllm-openai:nightly-deadbeef",
         "max_model_len": 4096, "kv_format": "bf16", "engine": "v"}


def _outcome(*, dl, boot, smoke):
    with tempfile.TemporaryDirectory() as _td:
        o = C.emit_capture(
            ei, confidence=SimpleNamespace(name="EXACT"),
            raw_verdict="fits-clean", profile_like="vllm/q",
            download_result=dl, boot_result=boot, smoke_result=smoke,
            compose_meta=cm_ok, kv_calc_version="kvcalc-v7",
            repo_root=Path(_td), ts="20260517T140000Z",
        )
        return json.loads(Path(o["paths"]["manifest"]).read_text())


# (1) THE on-rig Qwen2.5-0.5B case: download ok + boot ok + floor green,
#     every optional cap "unsmoked" (generic-dense declares none) -> the
#     §6.2 capability-scoped SUCCESS. MUST be "partial", explicitly NOT
#     "failed" (this is exactly the defect being corrected).
sm_floor = SmokeResult(
    smoke_capability_set=["plain-chat", "streaming"],
    results={"plain-chat": "green", "streaming": "green",
             "tool-call": "unsmoked", "reasoning-streaming": "unsmoked",
             "structured-output": "unsmoked", "vision": "unsmoked",
             "long-context": "unsmoked"},
    partial=True, results_detail={})
m1 = _outcome(dl=dl_ok, boot=res_ok, smoke=sm_floor)
check(m1["outcome"] == "partial",
      f"E-outcome-fix (1): floor-green + optionals-unsmoked (on-rig "
      f"Qwen2.5-0.5B) -> outcome=='partial' — §6.2 capability-scoped "
      f"SUCCESS (got {m1['outcome']!r})")
check(m1["outcome"] != "failed",
      "E-outcome-fix (1): partial-success is EXPLICITLY NOT 'failed' "
      "(the §1 confidently-wrong defect that was conflated)")

# (2) a "red" SMOKED capability is a genuine failure -> "failed".
sm_red = SmokeResult(
    smoke_capability_set=["plain-chat", "streaming"],
    results={"plain-chat": "green", "streaming": "red",
             "tool-call": "unsmoked"},
    partial=True,
    results_detail={"streaming": {"status": 404, "error": "no model"}})
m2 = _outcome(dl=dl_ok, boot=res_ok, smoke=sm_red)
check(m2["outcome"] == "failed",
      f"E-outcome-fix (2): a 'red' SMOKED cap -> outcome=='failed' "
      f"(red dominates partial) (got {m2['outcome']!r})")

# (3) boot NOT ok -> "failed" (stage hard-fail dominates; smoke may be
#     all-green floor but never ran a live server).
sm_green_floor = SmokeResult(
    smoke_capability_set=["plain-chat", "streaming"],
    results={"plain-chat": "green", "streaming": "green",
             "tool-call": "unsmoked"},
    partial=True, results_detail={})
m3 = _outcome(dl=dl_ok, boot=res_bad, smoke=sm_green_floor)
check(m3["outcome"] == "failed",
      f"E-outcome-fix (3): boot NOT ok -> outcome=='failed' (stage "
      f"hard-fail dominates) (got {m3['outcome']!r})")

# (3b) download NOT ok -> "failed" (earliest stage hard-fail dominates).
dl_bad = DownloadResult(ok=False, files=[], bytes=0, sha_verified=False,
                        failure="sha256-mismatch", local_dir=None)
m3b = _outcome(dl=dl_bad, boot=res_ok, smoke=sm_green_floor)
check(m3b["outcome"] == "failed",
      f"E-outcome-fix (3b): download NOT ok -> outcome=='failed' "
      f"(got {m3b['outcome']!r})")

# (4) everything attempted, all green, NOTHING unsmoked -> "ok".
sm_all = SmokeResult(
    smoke_capability_set=["plain-chat", "streaming", "tool-call",
                          "reasoning-streaming", "structured-output",
                          "vision", "long-context"],
    results={"plain-chat": "green", "streaming": "green",
             "tool-call": "green", "reasoning-streaming": "green",
             "structured-output": "green", "vision": "green",
             "long-context": "green"},
    partial=False, results_detail={})
m4 = _outcome(dl=dl_ok, boot=res_ok, smoke=sm_all)
check(m4["outcome"] == "ok",
      f"E-outcome-fix (4): all-green, nothing-unsmoked -> outcome=='ok' "
      f"(got {m4['outcome']!r})")

# Invariants preserved: the 3 states are exactly {ok,partial,failed}, the
# legacy buggy literal is GONE, `failure_class` stays null ([F]'s job), and
# `outcome` is still an input to the §6.2 fingerprint (only its VALUE honest).
check({m1["outcome"], m2["outcome"], m3["outcome"], m3b["outcome"],
       m4["outcome"]} == {"partial", "failed", "ok"},
      "E-outcome-fix: outcome is the honest 3-state {ok,partial,failed}")
check(all(m["outcome"] != "partial-or-failed"
          for m in (m1, m2, m3, m3b, m4)),
      "E-outcome-fix: the buggy 2-way 'partial-or-failed' literal is GONE")
for _m in (m1, m2, m3, m3b, m4):
    check(_m["failure_class"] is None,
          "E-outcome-fix: failure_class STILL null — E3 must NOT classify "
          "(§6.1 = [F]'s job; only `outcome` value changed)")
    check(isinstance(_m["submission_fingerprint"], str)
          and len(_m["submission_fingerprint"]) == 64,
          "E-outcome-fix: outcome still feeds the 64-hex §6.2 "
          "submission_fingerprint (composition unchanged, value honest)")

# ---------------------------------------------------------------------------
# v0.8.2 CONTRACT-1.1 — the pt1-gate-only emitter + the SHARED .last marker.
#
# `emit_gate_capture()` is a SEPARATE function (the `emit_override_capture`
# byte-preserving precedent) — NOT invoked by `emit_capture()`. It writes
# ONLY pt1-gate.json + a schema:2 manifest.json (gate-only terminated
# pre-download → no pt2/3/4/5), redacted via the SAME `_redact_text`.
# `write_last_marker()` is the ONE shared `.last` helper BOTH emitters call
# (the centralization mandate — gate-only is the commonest failure; if
# `.last` were [E]-only, `--submit-last` would silently miss gate captures).
# ---------------------------------------------------------------------------
with tempfile.TemporaryDirectory() as td:
    _root = Path(td)

    # (i) emit_capture() STILL writes ONLY pt1-4 + manifest (the V1
    #     RED-LINE: the gate emitter is separate, NOT invoked here).
    o_full = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="fits-clean", profile_like="vllm/x",
        download_result=dl, boot_result=res, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=_root, ts="20260518T100000Z",
    )
    _fnames = sorted(x.name for x in Path(o_full["dir"]).iterdir())
    check(_fnames == ["manifest.json", "pt1-gate.json", "pt2-download.json",
                      "pt3-boot.json", "pt4-smoke.json"],
          f"V1 RED-LINE: emit_capture() STILL writes ONLY pt1-4 + manifest "
          f"(the gate emitter is a SEPARATE fn) (got {_fnames})")

    # (ii) emit_gate_capture() — a C0 no-arch-row hard-block, der present.
    der_g = SimpleNamespace(profile={"arch": "ExoticForCausalLM",
                                     "weight_format": "bfloat16"})
    og = C.emit_gate_capture(
        slug="org/Exotic-Model",
        profile_like="vllm/minimal",
        abort_reason="engine-support-unknown/no-arch-row",
        confidence=SimpleNamespace(name="ESTIMATED_LOWER_BOUND"),
        raw_verdict=None,
        detail="[C0] engine-support-unknown/no-arch-row",
        der=der_g,
        hardware_sm=8.6,
        gpu_topology=(1, [24576], ["NVIDIA GeForce RTX 3090"]),
        club3090_commit="deadbeef",
        repo_root=_root,
        ts="20260518T101000Z",
    )
    _gnames = sorted(x.name for x in Path(og["dir"]).iterdir())
    check(_gnames == ["manifest.json", "pt1-gate.json"],
          f"emit_gate_capture: ONLY pt1-gate.json + manifest.json — no "
          f"pt2/3/4/5 (gate-only terminated pre-download) (got {_gnames})")
    gman = json.loads(Path(og["paths"]["manifest"]).read_text())
    gpt1 = json.loads(Path(og["paths"]["gate"]).read_text())
    check(gman["schema"] == 2 and gman["outcome"] == "hard-block"
          and gman["failure_class"] is None
          and gman["abort_reason"] == "engine-support-unknown/no-arch-row"
          and gman["is_gate_only"] is True,
          f"emit_gate_capture: manifest schema:2 / outcome:hard-block / "
          f"failure_class:null / exact abort_reason (got {sorted(gman)})")
    # Always-present row guaranteed; der present -> model/arch/quant set;
    # topology resolved best-effort from the injected gpu_topology.
    check(gman["model"] == "org/Exotic-Model"
          and gman["arch_family"] == "ExoticForCausalLM"
          and gman["quant_label"] == "bfloat16"
          and gman["topology_class"] == "1x24576MiB"
          and gman["topology_summary_canonical"]
              == "[(NVIDIA GeForce RTX 3090, 24576)]",
          "emit_gate_capture: der-present manifest carries model/arch/"
          "quant + best-effort topology")
    # post-C0 fields ALWAYS null on a gate-only bundle.
    check(all(gman[k] is None for k in (
              "selected_ctx", "kv_format", "smoke_capability_set",
              "engine_pin", "engine_version", "submission_fingerprint")),
          "emit_gate_capture: post-C0 fields ALWAYS null (never reached)")
    check(gpt1["schema"] == 2 and gpt1["point"] == "gate"
          and gpt1["abort_reason"]
              == "engine-support-unknown/no-arch-row"
          and gpt1["is_gate_only"] is True,
          "emit_gate_capture: pt1-gate carries the raw abort_reason (H2)")

    # (iii) pre-deriver (der=None) -> model/arch/quant null; topology null
    #       when neither override nor nvidia-smi.
    og2 = C.emit_gate_capture(
        slug="org/NoDeriver",
        profile_like="vllm/minimal",
        abort_reason="unsupported-format",
        confidence=None, raw_verdict=None,
        detail="deriver: unsupported-format",
        der=None, hardware_sm=None, gpu_topology=None,
        club3090_commit="deadbeef", repo_root=_root,
        ts="20260518T102000Z",
    )
    gm2 = json.loads(Path(og2["paths"]["manifest"]).read_text())
    # arch/quant are GUARANTEED null pre-deriver (no der.profile). Topology
    # is BEST-EFFORT (CONTRACT-1.1): null when neither --hardware-gpus nor
    # nvidia-smi — but on a rig WITH nvidia-smi the capture-only resolve
    # legitimately populates it (mirrors pull.py:853-855). So assert the
    # guaranteed-null invariants + topology being either null OR a valid
    # deterministic serialization (never fabricated/garbage).
    check(gm2["arch_family"] is None and gm2["quant_label"] is None
          and gm2["model"] == "org/NoDeriver",
          "emit_gate_capture: pre-deriver -> arch/quant null (no "
          "der.profile), model still the slug")
    _tc, _ts2 = gm2["topology_class"], gm2["topology_summary_canonical"]
    check((_tc is None and _ts2 is None)
          or (isinstance(_tc, str) and _tc.endswith("MiB")
              and isinstance(_ts2, str) and _ts2.startswith("[")),
          f"emit_gate_capture: topology is best-effort — null XOR a valid "
          f"deterministic serialization, never fabricated "
          f"(class={_tc!r} summary={_ts2!r})")

    # (iv) redaction: a leaky abort_reason/detail is _redact_text-scrubbed.
    og3 = C.emit_gate_capture(
        slug="org/Leaky",
        profile_like="vllm/minimal",
        abort_reason="hard-block",
        confidence=None, raw_verdict="wont-fit",
        detail="refused near /opt/ai/secret/model and /mnt/models/x",
        der=None, hardware_sm=8.6, gpu_topology=None,
        club3090_commit="deadbeef", repo_root=_root,
        ts="20260518T103000Z",
    )
    for _a in og3["paths"].values():
        _b = Path(_a).read_text()
        check("/opt/ai" not in _b and "/mnt/models" not in _b,
              f"emit_gate_capture: leaky path scrubbed from "
              f"{Path(_a).name} (same _redact_text discipline)")

    # (v) the SHARED `.last` marker — written by BOTH emitters (the
    #     centralization mandate). The last writer wins; it points at the
    #     RELATIVE bundle dir under .pull-captures/.
    _last = _root / ".pull-captures" / ".last"
    check(_last.is_file(),
          "write_last_marker: .pull-captures/.last exists after a capture")
    check(_last.read_text().strip()
          == str(Path(og3["dir"]).relative_to(_root / ".pull-captures")),
          "write_last_marker: .last points at the MOST-RECENT bundle "
          "(last-writer-wins; relative path)")
    # emit_capture() ALSO updates the SAME shared marker (not [E]-only).
    o_full2 = C.emit_capture(
        ei, confidence=SimpleNamespace(name="EXACT"),
        raw_verdict="fits-clean", profile_like="vllm/x",
        download_result=dl, boot_result=res, smoke_result=sm,
        compose_meta=compose_meta, kv_calc_version="kvcalc-v7",
        repo_root=_root, ts="20260518T104000Z",
    )
    check(_last.read_text().strip()
          == str(Path(o_full2["dir"]).relative_to(
              _root / ".pull-captures")),
          "write_last_marker: emit_capture() ALSO updates the SHARED "
          ".last (centralized — NOT wired into emit_capture only)")

# ---------------------------------------------------------------------------
# CONTRACT-5 G1 — live-topology verify gate (F6).
#
# `topology_summary_canonical` live wiring
# (einput.topology_summary <- pull._topology_summary_canonical <-
# detect_gpu_topology) was fixture-asserted ONLY. This gate confirms the
# REAL on-disk `.pull-captures/<slug>/<utc-ts>/manifest.json` corpus carries
# a correctly-serialized `topology_summary_canonical`: a `[(name, vram), …]`
# list that is byte-identical to a DETERMINISTIC re-serialization of its own
# sorted `(gpu_name, vram_mib)` tuples (the §6.2 stability property). This
# is a VERIFICATION ASSERTION over whatever real corpus EXISTS — NOT new
# production code, and NOT a precondition. `.pull-captures/` is gitignored
# runtime state: populated only after a real on-rig pull, ALWAYS absent on
# a fresh clone / in CI / after cleanup. The G1 invariant is the topology
# SERIALIZATION FORMAT of any captures present, not that the runtime corpus
# is populated — so this SKIPS (never fails) when absent, and asserts the
# format on each manifest that IS present (on-rig).
import re as _re_g1

REPO = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
_cap_root = REPO / ".pull-captures"
_manifests = sorted(_cap_root.glob("*/*/manifest.json")) if \
    _cap_root.is_dir() else []
if _manifests:
    print(f"G1: verifying topology serialization on {len(_manifests)} "
          f"on-disk capture manifest(s) under {_cap_root}")
else:
    print(f"G1: SKIP — no on-disk capture corpus under {_cap_root} "
          f"(gitignored runtime state; expected absent in CI / fresh "
          f"clone). Format assertions run only on present manifests.")

# Parse "[(NAME, VRAM), (NAME, VRAM), ...]" -> [(name:str, vram:int), ...]
_TUP = _re_g1.compile(r"\(([^,]+),\s*(\d+)\)")


def _parse_topo(s):
    if not (isinstance(s, str) and s.startswith("[") and s.endswith("]")):
        return None
    return [(m.group(1).strip(), int(m.group(2)))
            for m in _TUP.finditer(s)]


def _reserialize(tuples):
    t = sorted((str(n), int(v)) for n, v in tuples)
    return "[" + ", ".join(f"({n}, {v})" for n, v in t) + "]"


for _mf in _manifests:
    _man = json.loads(_mf.read_text())
    _raw = _man.get("topology_summary_canonical")
    _tuples = _parse_topo(_raw)
    check(_tuples is not None and len(_tuples) >= 1,
          f"G1: {_mf.parent.name} topology_summary_canonical parses to a "
          f"non-empty (name, vram) list (got {_raw!r})")
    if _tuples:
        # Deterministic-sorted-serialization property: the on-disk string
        # equals a re-serialization of its OWN sorted tuples (stable across
        # runs/rigs for the consensus key — §6.2 verbatim).
        check(_raw == _reserialize(_tuples),
              f"G1: {_mf.parent.name} topology_summary_canonical is the "
              f"deterministic sorted (gpu_name, vram_mib) serialization "
              f"(got {_raw!r}, expected {_reserialize(_tuples)!r})")
        check(all(isinstance(n, str) and n and isinstance(v, int) and v > 0
                  for n, v in _tuples),
              f"G1: {_mf.parent.name} each tuple is (non-empty gpu_name, "
              f"positive vram_mib)")

if failures:
    print(f"\n{len(failures)} assertion(s) failed.", file=sys.stderr)
    sys.exit(1)
print("\nAll E3 boot + smoke + capture (CONTRACT-4) + G1 live-topology "
      "verify assertions passed.")
PY

echo "test-pullemit-capture.sh OK"
