Admit live E2 from a value-safe custody receipt

WH-ENG-20260822-AUDIT-E2-02 projected and then aborted: admit-plane had no
receipt adapter, so the runner sent zero packets. Consume custody receipts
as handles only, keep unconnected admission fail-closed, and retire -02.

Assistant: grok
Assistant-Session: 01a02670-3345-76f2-a014-70fde8e2a2bb
This commit is contained in:
tegwick 2026-08-22 21:31:49 +02:00
parent b6c1806680
commit 45548e44a2
14 changed files with 221 additions and 42 deletions

View file

@ -69,8 +69,10 @@ boundary always holds.
- `T01` is complete: the rules of engagement were accepted on 2026-08-21. - `T01` is complete: the rules of engagement were accepted on 2026-08-21.
- `T02` is complete: the per-axis attacker model is recorded in - `T02` is complete: the per-axis attacker model is recorded in
`docs/attacker-model.md`. `docs/attacker-model.md`.
- `T03` is in progress: `WH-ENG-20260822-AUDIT-E2-01` expired at 18:15Z with - `T03` is in progress: `WH-ENG-20260822-AUDIT-E2-01` expired unused;
no live run. A new engagement ID is required for a retry. `WH-ENG-20260822-AUDIT-E2-02` aborted after projection because admission
had no receipt adapter. Zero packets. A later live run needs a new ID and
`admit-plane --receipt`.
- `T04` is complete for every applicable E2 probe: generic and audit-core - `T04` is complete for every applicable E2 probe: generic and audit-core
shaped fixtures fail known-bad and pass known-good in-process. shaped fixtures fail known-bad and pass known-good in-process.
- `T05` is in progress: 24-hour cadence, offline evaluator and in-process - `T05` is in progress: 24-hour cadence, offline evaluator and in-process

View file

@ -50,7 +50,7 @@ retired.
```sh ```sh
PYTHONPATH=src python3 -m whitehat_security.cli validate-engagement engagements/<record>.json PYTHONPATH=src python3 -m whitehat_security.cli validate-engagement engagements/<record>.json
PYTHONPATH=src python3 -m whitehat_security.cli admit-plane engagements/<record>.json targets/<target>.json PYTHONPATH=src python3 -m whitehat_security.cli admit-plane engagements/<record>.json targets/<target>.json --receipt <custody-receipt.json>
``` ```
7. Confirm target identity/revision and probe attribution before the first 7. Confirm target identity/revision and probe attribution before the first

View file

@ -16,10 +16,10 @@ A live E2 run is admitted only when every item below is true:
5. The runner namespace is `whitehat`, the image digest is pinned, and 5. The runner namespace is `whitehat`, the image digest is pinned, and
engagement labels are present. Live E3 also names a `database`. Live engagement labels are present. Live E3 also names a `database`. Live
capacity also names an `aggressor_ceiling`. capacity also names an `aggressor_ceiling`.
6. The custody broker projected the registered number of identity *handles* 6. A value-safe custody **receipt** is supplied to `admit-plane --receipt`.
(two for E2, one runtime for E3, none for capacity fixtures). Credential The receipt names handles, mount keys, expiry and `secret_values_observed:
values are not returned to the operator or agent. Live classes fail closed false`. It must not contain token values. Without a receipt, live admission
while the custody broker is unconnected. fails closed and requests no credential.
7. The kill switch is clear. 7. The kill switch is clear.
8. The rate watcher is attached. Cleanup revokes the lease. 8. The rate watcher is attached. Cleanup revokes the lease.
@ -38,7 +38,7 @@ requested.
```sh ```sh
PYTHONPATH=src python3 -m whitehat_security.cli validate-targets targets PYTHONPATH=src python3 -m whitehat_security.cli validate-targets targets
PYTHONPATH=src python3 -m whitehat_security.cli admit-plane engagements/<record>.json targets/<target>.json PYTHONPATH=src python3 -m whitehat_security.cli admit-plane engagements/<record>.json targets/<target>.json --receipt <custody-receipt.json>
PYTHONPATH=src python3 -m whitehat_security.cli kill-switch PYTHONPATH=src python3 -m whitehat_security.cli kill-switch
PYTHONPATH=src python3 -m whitehat_security.cli deliver evidence/<run>.json --outbox outbox PYTHONPATH=src python3 -m whitehat_security.cli deliver evidence/<run>.json --outbox outbox
``` ```
@ -47,7 +47,8 @@ Create `plane/KILL` to abort independently of the runner process.
## Retired identifiers ## Retired identifiers
`WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`, and `WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`,
`WH-ENG-20260822-AUDIT-E2-01` are terminal. The last of those expired at `WH-ENG-20260822-AUDIT-E2-01`, and `WH-ENG-20260822-AUDIT-E2-02` are terminal.
18:15Z with no projection-ready notice and zero packets. A later live E2 run `-02` projected and then aborted: admission had no receipt adapter, so the
needs a new ID, a new window, and a fresh attended projection. runner sent zero packets. A later live E2 run needs a new ID, a new window,
a custody receipt, and `admit-plane --receipt`.

View file

@ -1,6 +1,8 @@
{ {
"engagement_id": "WH-ENG-20260822-AUDIT-E2-02", "engagement_id": "WH-ENG-20260822-AUDIT-E2-02",
"status": "approved", "status": "aborted",
"aborted_at": "2026-08-22T19:21:39Z",
"abort_reason": "Attended projection succeeded at 19:17:54Z and the runner became Ready, but admit-plane failed closed: no live adapter consumed the value-safe custody receipt. Zero packets. Runner deleted 19:21:07Z; custody cleanup 19:21:39Z. Not E2 evidence. Identifier is terminal.",
"proposal_at": "2026-08-22T18:36:50Z", "proposal_at": "2026-08-22T18:36:50Z",
"proposal_reason": "Fresh attended retry after WH-ENG-20260822-AUDIT-E2-01 expired with zero packets. The terminal identifier, identities and fixtures are not reused.", "proposal_reason": "Fresh attended retry after WH-ENG-20260822-AUDIT-E2-01 expired with zero packets. The terminal identifier, identities and fixtures are not reused.",
"authorization_id": "operator-session-2026-08-22-e2-02-approval", "authorization_id": "operator-session-2026-08-22-e2-02-approval",

View file

@ -1,21 +1,13 @@
# WH-ENG-20260822-AUDIT-E2-02 # WH-ENG-20260822-AUDIT-E2-02
Status: **approved and waiting**. The engagement is fail-closed until Status: **aborted**. Not E2 evidence.
`2026-08-22T19:15:00Z` and until custody reports projection ready.
| Gate | State | Attended projection succeeded at `2026-08-22T19:17:54Z` and the runner pod
| --- | --- | became Ready. Target validation, engagement validation and the kill switch
| Operator approval | `2026-08-22T18:42:50Z` in the coordinating session | passed. `admit-plane` then failed closed because this repository had no
| Owner acknowledgement | `2026-08-22T18:45:39Z` after direct artifact review | adapter for a value-safe custody receipt. The runner was never invoked.
| Foundational plane | applied by ops-mason `c26a6e5` | Zero packets. Runner deleted `19:21:07Z`; custody cleanup `19:21:39Z`.
| Window | `2026-08-22T19:15:00Z``19:30:00Z` | Identifier is terminal.
| Image | `sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6` |
| Custody projection | pending; do not mint before 19:15Z |
| Runner pod | not applied |
| Packets | none |
Offline preflight must fail closed before the window and until the live custody The fix is `admit-plane --receipt <projection-receipt.json>`, which issues a
broker confirms the two exact mounted identities. plane lease from handles only.
Invocation, including required `--forged-event whitehat-e2-forged-b-20260822-02`:
`engagements/runtime/WH-ENG-20260822-AUDIT-E2-02-invocation.md`.

View file

@ -14,6 +14,8 @@ from `whitehat admit-plane`. `WH-ENG-20260821-AUDIT-E2` and
`WH-ENG-20260821-TENANT-E2` are cancelled and must not be reused. The pod `WH-ENG-20260821-TENANT-E2` are cancelled and must not be reused. The pod
manifest under `runtime/` is bound to a cancelled ID and must not be applied. manifest under `runtime/` is bound to a cancelled ID and must not be applied.
`WH-ENG-20260822-AUDIT-E2-01` expired at `2026-08-22T18:15:00Z` with no live `WH-ENG-20260822-AUDIT-E2-01` expired unused. `WH-ENG-20260822-AUDIT-E2-02`
run. Do not reuse it. A later attempt needs a new identifier and window. aborted after projection because `admit-plane` had no receipt adapter; zero
packets. Neither identifier may be reused. Live admission requires
`--receipt` of a value-safe custody projection.

View file

@ -3,8 +3,8 @@
This repository does not apply cluster objects, request credentials, or send This repository does not apply cluster objects, request credentials, or send
packets. `ops-mason` provisions the foundational plane from these files packets. `ops-mason` provisions the foundational plane from these files
**before** a fresh engagement is proposed. Cancelled or expired IDs **before** a fresh engagement is proposed. Cancelled or expired IDs
`WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`, and `WH-ENG-20260821-AUDIT-E2`, `WH-ENG-20260821-TENANT-E2`,
`WH-ENG-20260822-AUDIT-E2-01` are terminal. `WH-ENG-20260822-AUDIT-E2-01`, and `WH-ENG-20260822-AUDIT-E2-02` are terminal.
## Ordering ## Ordering

View file

@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "whitehat-projection-receipt/v1",
"type": "object",
"additionalProperties": false,
"required": [
"engagement_id", "projected_at", "expires_at", "identities",
"mounted_secret", "mounted_keys", "target_ready", "secret_values_observed"
],
"properties": {
"engagement_id": {"type": "string", "minLength": 1},
"projected_at": {"type": "string", "format": "date-time"},
"expires_at": {"type": "string", "format": "date-time"},
"identities": {"type": "array", "minItems": 1, "items": {"type": "string"}},
"mounted_secret": {"type": "string", "pattern": "^whitehat/"},
"mounted_keys": {"type": "array", "items": {"type": "string"}},
"target_image_matches": {"type": "boolean"},
"target_ready": {"const": true},
"secret_values_observed": {"const": false}
}
}

View file

@ -13,7 +13,7 @@ from .e3 import CADENCE, PROBES, e3_calibration
from .engagement import AuthorizationError, Engagement from .engagement import AuthorizationError, Engagement
from .fixtures import FixtureService, probe_suite from .fixtures import FixtureService, probe_suite
from .model import RunReport, utc_now from .model import RunReport, utc_now
from .plane import KillSwitch, admit, default_broker, retired_ids from .plane import KillSwitch, ReceiptBroker, admit, default_broker, retired_ids
from .reporting import queue_risk_nexus, risk_nexus_message from .reporting import queue_risk_nexus, risk_nexus_message
from .targets import load_catalog, load_registration from .targets import load_catalog, load_registration
@ -90,6 +90,7 @@ def main(argv: list[str] | None = None) -> None:
admit_plane = commands.add_parser("admit-plane") admit_plane = commands.add_parser("admit-plane")
admit_plane.add_argument("engagement") admit_plane.add_argument("engagement")
admit_plane.add_argument("registration") admit_plane.add_argument("registration")
admit_plane.add_argument("--receipt", help="value-safe custody projection receipt")
commands.add_parser("kill-switch") commands.add_parser("kill-switch")
deliver = commands.add_parser("deliver") deliver = commands.add_parser("deliver")
deliver.add_argument("report") deliver.add_argument("report")
@ -138,8 +139,11 @@ def main(argv: list[str] | None = None) -> None:
try: try:
record = Engagement.load(args.engagement) record = Engagement.load(args.engagement)
registration = load_registration(args.registration) registration = load_registration(args.registration)
broker = (
ReceiptBroker.load(args.receipt) if args.receipt else default_broker(record)
)
lease = admit(engagement=record, registration=registration, lease = admit(engagement=record, registration=registration,
broker=default_broker(record), kill_switch=KillSwitch(), broker=broker, kill_switch=KillSwitch(),
retired=retired_ids()) retired=retired_ids())
except (AuthorizationError, OSError, ValueError, json.JSONDecodeError) as error: except (AuthorizationError, OSError, ValueError, json.JSONDecodeError) as error:
print(f"not authorized: {error}", file=sys.stderr) print(f"not authorized: {error}", file=sys.stderr)

View file

@ -43,6 +43,10 @@ class Engagement:
raise AuthorizationError( raise AuthorizationError(
"engagement window elapsed with no live run; identifier must not be reused" "engagement window elapsed with no live run; identifier must not be reused"
) )
if self.raw.get("status") == "aborted":
raise AuthorizationError(
"engagement aborted without target evidence; identifier must not be reused"
)
if self.raw.get("status") == "proposed": if self.raw.get("status") == "proposed":
raise AuthorizationError( raise AuthorizationError(
"engagement is proposed; operator approval and owner acknowledgement are pending" "engagement is proposed; operator approval and owner acknowledgement are pending"

View file

@ -16,6 +16,7 @@ TERMINAL_IDS = frozenset({
"WH-ENG-20260821-AUDIT-E2", "WH-ENG-20260821-AUDIT-E2",
"WH-ENG-20260821-TENANT-E2", "WH-ENG-20260821-TENANT-E2",
"WH-ENG-20260822-AUDIT-E2-01", "WH-ENG-20260822-AUDIT-E2-01",
"WH-ENG-20260822-AUDIT-E2-02",
}) })
APPROVAL_CLASSES = { APPROVAL_CLASSES = {
"fixture-e2": frozenset({"e2-differential"}), "fixture-e2": frozenset({"e2-differential"}),
@ -42,7 +43,7 @@ def retired_ids(engagements_dir: str | Path | None = None) -> set[str]:
data = json.loads(path.read_text(encoding="utf-8")) data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError, ValueError): except (OSError, json.JSONDecodeError, ValueError):
continue continue
if data.get("status") in {"cancelled", "expired"} and data.get("engagement_id"): if data.get("status") in {"cancelled", "expired", "aborted"} and data.get("engagement_id"):
retired.add(data["engagement_id"]) retired.add(data["engagement_id"])
return retired return retired
@ -113,6 +114,82 @@ class CredentialBroker(Protocol):
... ...
FORBIDDEN_RECEIPT_KEYS = {
"token", "token_a", "token_b", "password", "secret", "secret_value",
"bearer", "credential", "value", "senders.json",
}
def _receipt_contains_secret_material(value: Any) -> bool:
if isinstance(value, dict):
for key, child in value.items():
lowered = str(key).lower().replace("-", "_")
if lowered in FORBIDDEN_RECEIPT_KEYS:
return True
if _receipt_contains_secret_material(child):
return True
return False
if isinstance(value, list):
return any(_receipt_contains_secret_material(item) for item in value)
return False
class ReceiptBroker:
"""Consume a value-safe custody receipt. Never reads or stores secret bytes."""
def __init__(self, receipt: dict[str, Any]) -> None:
if _receipt_contains_secret_material(receipt):
raise AuthorizationError("projection receipt contains secret material")
if receipt.get("secret_values_observed") is not False:
raise AuthorizationError("projection receipt did not prove secret values were unobserved")
self.receipt = receipt
@classmethod
def load(cls, path: str | Path) -> "ReceiptBroker":
return cls(json.loads(Path(path).read_text(encoding="utf-8")))
def project(self, engagement: Engagement, registration: dict[str, Any]
) -> tuple[IdentityHandle, ...]:
if self.receipt.get("engagement_id") != engagement.raw["engagement_id"]:
raise AuthorizationError("projection receipt engagement_id does not match")
if not self.receipt.get("target_ready"):
raise AuthorizationError("projection receipt does not show a ready target")
expires_at = self.receipt.get("expires_at")
if not expires_at:
raise AuthorizationError("projection receipt missing expires_at")
expiry = datetime.fromisoformat(str(expires_at).replace("Z", "+00:00"))
if expiry.tzinfo is None:
raise AuthorizationError("projection receipt expiry must include a timezone")
if datetime.now(UTC) > expiry.astimezone(UTC):
raise AuthorizationError("projection receipt has expired")
keys = list(self.receipt.get("mounted_keys") or [])
count = int(registration["identities"]["count"])
if count == 2 and keys != ["token-a", "token-b"]:
raise AuthorizationError("E2 receipt must mount exactly token-a and token-b")
names = list(self.receipt.get("identities") or [])
if len(names) != count:
raise AuthorizationError("projection receipt identity count does not match registration")
secret = str(self.receipt.get("mounted_secret") or "")
if not secret.startswith("whitehat/"):
raise AuthorizationError("projection receipt must mount in namespace whitehat")
lease_id = f"{engagement.raw['engagement_id']}:{self.receipt.get('projected_at', '')}"
if count == 2:
return (
IdentityHandle("owner", "/var/run/secrets/whitehat/token-b", lease_id, expires_at),
IdentityHandle("attacker", "/var/run/secrets/whitehat/token-a", lease_id, expires_at),
)
if count == 1:
return (
IdentityHandle("runtime", "/var/run/secrets/whitehat/runtime", lease_id, expires_at),
)
return ()
def revoke(self, lease_id: str) -> None:
raise AuthorizationError(
"receipt broker does not hold credentials; custody must revoke the projection"
)
class UnconnectedCustodyBroker: class UnconnectedCustodyBroker:
"""Fail-closed live broker. It never requests or returns credential values.""" """Fail-closed live broker. It never requests or returns credential values."""

View file

@ -1,5 +1,6 @@
import json import json
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path
import pytest import pytest
@ -63,8 +64,13 @@ def test_pending_owner_acknowledgement_fails_closed(tmp_path):
load(tmp_path, data) load(tmp_path, data)
def test_retry_record_is_authorized_only_inside_its_window(): def test_retry_record_is_authorized_only_inside_its_window(tmp_path):
path = "engagements/2026-08-22-audit-core-e2-02.json" data = json.loads(Path("engagements/2026-08-22-audit-core-e2-02.json").read_text())
data["status"] = "approved"
data.pop("aborted_at", None)
data.pop("abort_reason", None)
path = tmp_path / "retry.json"
path.write_text(json.dumps(data), encoding="utf-8")
with pytest.raises(AuthorizationError, match="has not started"): with pytest.raises(AuthorizationError, match="has not started"):
Engagement.load(path, now=datetime(2026, 8, 22, 19, 14, tzinfo=UTC)) Engagement.load(path, now=datetime(2026, 8, 22, 19, 14, tzinfo=UTC))
engagement = Engagement.load(path, now=datetime(2026, 8, 22, 19, 15, tzinfo=UTC)) engagement = Engagement.load(path, now=datetime(2026, 8, 22, 19, 15, tzinfo=UTC))
@ -73,6 +79,14 @@ def test_retry_record_is_authorized_only_inside_its_window():
Engagement.load(path, now=datetime(2026, 8, 22, 19, 31, tzinfo=UTC)) Engagement.load(path, now=datetime(2026, 8, 22, 19, 31, tzinfo=UTC))
def test_aborted_record_fails_closed():
with pytest.raises(AuthorizationError, match="aborted"):
Engagement.load(
"engagements/2026-08-22-audit-core-e2-02.json",
now=datetime(2026, 8, 22, 19, 20, tzinfo=UTC),
)
def test_elapsed_record_fails_closed_even_inside_old_window(tmp_path): def test_elapsed_record_fails_closed_even_inside_old_window(tmp_path):
data = record() data = record()
data["status"] = "expired" data["status"] = "expired"

View file

@ -10,7 +10,7 @@ from whitehat_security.fixtures import FixtureService, probe_suite
from whitehat_security.model import RunReport from whitehat_security.model import RunReport
from whitehat_security.e3 import e3_calibration from whitehat_security.e3 import e3_calibration
from whitehat_security.plane import ( from whitehat_security.plane import (
KillSwitch, LocalBroker, RateWatcher, UnconnectedCustodyBroker, KillSwitch, LocalBroker, RateWatcher, ReceiptBroker, UnconnectedCustodyBroker,
admit, cleanup, retired_ids, admit, cleanup, retired_ids,
) )
from whitehat_security.reporting import queue_risk_nexus from whitehat_security.reporting import queue_risk_nexus
@ -71,6 +71,7 @@ def test_retired_ids_include_cancelled_records():
assert "WH-ENG-20260821-AUDIT-E2" in ids assert "WH-ENG-20260821-AUDIT-E2" in ids
assert "WH-ENG-20260821-TENANT-E2" in ids assert "WH-ENG-20260821-TENANT-E2" in ids
assert "WH-ENG-20260822-AUDIT-E2-01" in ids assert "WH-ENG-20260822-AUDIT-E2-01" in ids
assert "WH-ENG-20260822-AUDIT-E2-02" in ids
def test_fixture_plane_admits_and_projects_handles_without_secrets(tmp_path): def test_fixture_plane_admits_and_projects_handles_without_secrets(tmp_path):
@ -165,6 +166,63 @@ def test_unconnected_broker_requests_no_credential(tmp_path):
) )
def test_receipt_broker_issues_lease_without_secret_values(tmp_path):
engagement = load_engagement(tmp_path, fixture_record(
approval_class="live-e2", environment="build",
target_id="audit-core",
routes=["POST /v1/events"],
plane_namespace="whitehat",
runner_image_digest="sha256:abc",
))
receipt = {
"engagement_id": "WH-ENG-FIXTURE-1",
"projected_at": "2026-08-22T12:00:00Z",
"expires_at": "2099-01-01T00:00:00Z",
"identities": ["whitehat-e2-a", "whitehat-e2-b"],
"mounted_secret": "whitehat/whitehat-e2-audit-credentials",
"mounted_keys": ["token-a", "token-b"],
"target_ready": True,
"secret_values_observed": False,
}
lease = admit(
engagement=engagement, registration=load_registration("targets/audit-core-e2.json"),
broker=ReceiptBroker(receipt), kill_switch=KillSwitch(tmp_path / "KILL"),
now=NOW, retired=set(),
)
assert {handle.role for handle in lease.identities} == {"owner", "attacker"}
rendered = repr(lease) + repr(lease.identities) + json.dumps(receipt)
assert "token_urlsafe" not in rendered
with pytest.raises(AuthorizationError, match="custody must revoke"):
cleanup(lease, ReceiptBroker(receipt))
def test_receipt_broker_rejects_secret_material(tmp_path):
engagement = load_engagement(tmp_path, fixture_record(
approval_class="live-e2", environment="build",
target_id="audit-core",
routes=["POST /v1/events"],
plane_namespace="whitehat",
runner_image_digest="sha256:abc",
))
receipt = {
"engagement_id": "WH-ENG-FIXTURE-1",
"projected_at": "2026-08-22T12:00:00Z",
"expires_at": "2099-01-01T00:00:00Z",
"identities": ["whitehat-e2-a", "whitehat-e2-b"],
"mounted_secret": "whitehat/whitehat-e2-audit-credentials",
"mounted_keys": ["token-a", "token-b"],
"target_ready": True,
"secret_values_observed": False,
"token": "must-not-appear",
}
with pytest.raises(AuthorizationError, match="secret material"):
admit(
engagement=engagement, registration=load_registration("targets/audit-core-e2.json"),
broker=ReceiptBroker(receipt), kill_switch=KillSwitch(tmp_path / "KILL"),
now=NOW, retired=set(),
)
def test_e3_is_not_admitted_by_the_e2_plane(tmp_path): def test_e3_is_not_admitted_by_the_e2_plane(tmp_path):
engagement = load_engagement(tmp_path, fixture_record( engagement = load_engagement(tmp_path, fixture_record(
approval_class="e3", techniques=["e3-rls"], approval_class="e3", techniques=["e3-rls"],

View file

@ -163,8 +163,10 @@ target request and are retired. ops-mason applied the foundational plane on
2026-08-22T09:52:55Z; audit-core acknowledged at 2026-08-22T10:04:56Z 2026-08-22T09:52:55Z; audit-core acknowledged at 2026-08-22T10:04:56Z
(`32afceb`) after the fixture/digest correction `1418dac`. Offline preflight (`32afceb`) after the fixture/digest correction `1418dac`. Offline preflight
recorded a window that elapsed at 18:15Z with no projection-ready notice and recorded a window that elapsed at 18:15Z with no projection-ready notice and
zero packets. That identifier is expired and must not be reused. A later live zero packets. `WH-ENG-20260822-AUDIT-E2-02` projected at 19:17:54Z and the
E2 run needs a new ID, a new window, and a fresh attended projection. runner became Ready, then `admit-plane` failed closed because no receipt
adapter existed; zero packets, cleanup complete 19:21:39Z. Both identifiers
are terminal. Live admission now consumes a value-safe custody receipt.
Whitehat will not relabel tenant-engine to finish this task. Whitehat will not relabel tenant-engine to finish this task.
### T04 — Prove the probes fail ### T04 — Prove the probes fail