diff --git a/Makefile b/Makefile index 9426f7c..23b0b25 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,4 @@ test: fixture-evidence: PYTHONPATH=src python3 -m whitehat_security.cli fixtures --output evidence/offline-calibration.json PYTHONPATH=src python3 -m whitehat_security.cli e3-fixtures --output evidence/offline-e3-calibration.json + PYTHONPATH=src python3 -m whitehat_security.cli capacity-fixture --output evidence/offline-capacity-calibration.json diff --git a/SCOPE.md b/SCOPE.md index 2369d0c..3806e77 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -65,29 +65,16 @@ boundary always holds. ## Current state - Repository status: active. -- Active plan: `WHITEHAT-WP-0001`. Meantime polish: `WHITEHAT-WP-0002` through - `WHITEHAT-WP-0005` (Railiance WP-0025 custody adapter and fail-closed - admission). -- `T01` is complete: the rules of engagement were accepted on 2026-08-21. -- `T02` is complete: the per-axis attacker model is recorded in - `docs/attacker-model.md`. -- `T03` is complete for currently applicable E2 targets: `audit-core` has - dated pass `WH-ENG-20260822-AUDIT-E2-03`; `tenant-engine` remains - `not_applicable`. `flex-auth` is still pending. `-01` and `-02` are - terminal without E2 evidence. -- `T04` is complete for every applicable E2 probe: generic and audit-core - shaped fixtures fail known-bad and pass known-good in-process. -- `T05` is in progress: 24-hour cadence, offline evaluator and in-process - calibration exist; `platform-pg` is `not_applicable` for the requested - runtime identity. -- `T06` is in progress: the characterization evaluator is calibrated - in-process; `shared-substrate` is pending a live window and ceiling. -- `T07` is complete: the `-03` sanitized pass was delivered to `risk-nexus` - as `40e3f825-fc70-4091-96d2-9ab01d42184a`. -- `T08` is the governed test plane contract. Cluster provisioning is outside - this repository. -- `-03` is terminal. A later live run needs a new engagement ID, plane - admission, and approvals. `flex-auth` and P1/P2 remain unrun. +- Finished: `WHITEHAT-WP-0001` through `WHITEHAT-WP-0005`. Residual live + evidence is owned by `WHITEHAT-WP-0006` and is blocked on authorization. +- `WHITEHAT-WP-0001` T01–T08 are complete for every applicable target. + `audit-core` has dated E2 pass `WH-ENG-20260822-AUDIT-E2-03`. + `tenant-engine` E2 and `platform-pg` E3 stay `not_applicable`. `flex-auth` + E2 and `shared-substrate` P1/P2 stay `pending`. Offline E2, E3 and P1/P2 + harnesses fail known-bad and pass known-good in-process. +- `-01`, `-02` and `-03` are terminal. A later live run needs a new + engagement ID, plane admission, and the approvals required by the rules of + engagement. `WHITEHAT-WP-0006` owns that work and authorizes no packet. ## Relationships @@ -103,4 +90,6 @@ boundary always holds. 2. Read [the rules of engagement](docs/rules-of-engagement.md) before any probe design or execution. 3. Read [`WHITEHAT-WP-0001`](workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md) - for active tasks and sequencing. + for the finished applicable-target artifacts, then + [`WHITEHAT-WP-0006`](workplans/WHITEHAT-WP-0006-authorized-live-residuals.md) + for unauthorized live residuals. diff --git a/docs/e3-cadence.md b/docs/e3-cadence.md index e79f31f..a1c023a 100644 --- a/docs/e3-cadence.md +++ b/docs/e3-cadence.md @@ -20,6 +20,9 @@ PYTHONPATH=src python3 -m whitehat_security.cli e3-fixtures `e3-fixtures` calibrates the seven expected outcomes in-process, including keeping the SQL-compromise reset as `inconclusive`. It opens no database connection and is not target assurance. +`evidence/offline-e3-calibration.json` is the fixture artifact. A live +database run is owned by `WHITEHAT-WP-0006` and still requires a runtime-safe +surface, named database, and window. The run checks the conformance view, unset-GUC reads, wrong-tenant reads and writes, `BYPASSRLS` on the runtime identity, and unsafe `SECURITY DEFINER` diff --git a/docs/noisy-neighbour-runbook.md b/docs/noisy-neighbour-runbook.md index 4d4da78..cc0b3c2 100644 --- a/docs/noisy-neighbour-runbook.md +++ b/docs/noisy-neighbour-runbook.md @@ -23,5 +23,10 @@ The evaluator is calibrated offline with: PYTHONPATH=src python3 -m whitehat_security.cli capacity-fixture ``` -That command generates no load and is not P1/P2 target evidence. +That command generates no load and is not P1/P2 target evidence. Known-good +binds and stays within ceiling; known-bad detects an unbound governor, an +exceeded ceiling, and a missing neighbour sample. +`evidence/offline-capacity-calibration.json` is the fixture artifact. A live +run is owned by `WHITEHAT-WP-0006` and still requires an operator-approved +window and aggressor ceiling. diff --git a/docs/operator-runbook.md b/docs/operator-runbook.md index 8e8c05c..a7b306c 100644 --- a/docs/operator-runbook.md +++ b/docs/operator-runbook.md @@ -15,8 +15,9 @@ make fixture-evidence Success requires every read/write probe to pass against the enforcing fixture and to produce a finding against the fixture with its tenant predicate removed. -`make fixture-evidence` also writes `evidence/offline-e3-calibration.json`. -Both files are calibration evidence, not target assurance. +`make fixture-evidence` also writes `evidence/offline-e3-calibration.json` and +`evidence/offline-capacity-calibration.json`. Those files are calibration +evidence, not target assurance. ## Test plane diff --git a/evidence/README.md b/evidence/README.md index f368230..1bc4492 100644 --- a/evidence/README.md +++ b/evidence/README.md @@ -1,9 +1,10 @@ # Evidence -This directory stores sanitized run artifacts. `offline-calibration.json` and -`offline-e3-calibration.json` are generated from repository-created fixtures -and prove only that the harness distinguishes known-good from known-bad -behavior. They are not target assurance. `WH-ENG-20260822-AUDIT-E2-02-abort.json` +This directory stores sanitized run artifacts. `offline-calibration.json`, +`offline-e3-calibration.json` and `offline-capacity-calibration.json` are +generated from repository-created fixtures and prove only that the harness +distinguishes known-good from known-bad behavior. They are not target +assurance. `WH-ENG-20260822-AUDIT-E2-02-abort.json` is an abort record (`evidence_class: abort`), not an E2 pass or finding. `WH-ENG-20260822-AUDIT-E2-03.json` is the first authorized target pass; SHA-256 `2d5a21141b78024a5334881e2b7fd62a69c46931057f77515a6c6f18ec497593`. A pass diff --git a/evidence/offline-capacity-calibration.json b/evidence/offline-capacity-calibration.json new file mode 100644 index 0000000..0d1062b --- /dev/null +++ b/evidence/offline-capacity-calibration.json @@ -0,0 +1,98 @@ +{ + "ended_at": "2026-09-01T18:25:59.344646Z", + "evidence_class": "fixture", + "known_bad": [ + { + "aggressor_ceiling": 10, + "aggressor_peak": 10, + "case": "unbound_governor", + "governor_bound": false, + "neighbour_degradation": { + "aggressor": { + "error_rate_increase_points": 0.01, + "latency_increase_percent": 150.0, + "throughput_decrease_percent": -20.0 + }, + "neighbour": { + "error_rate_increase_points": 0.02, + "latency_increase_percent": 50.0, + "throughput_decrease_percent": 25.0 + } + }, + "outcome": "finding", + "reasons": [ + "declared governor did not bind" + ] + }, + { + "aggressor_ceiling": 10, + "aggressor_peak": 11, + "case": "exceeded_ceiling", + "governor_bound": true, + "neighbour_degradation": { + "aggressor": { + "error_rate_increase_points": 0.01, + "latency_increase_percent": 150.0, + "throughput_decrease_percent": -20.0 + }, + "neighbour": { + "error_rate_increase_points": 0.02, + "latency_increase_percent": 50.0, + "throughput_decrease_percent": 25.0 + } + }, + "outcome": "aborted", + "reasons": [ + "probe exceeded its authorized allowance" + ] + }, + { + "aggressor_ceiling": 10, + "aggressor_peak": 10, + "case": "missing_neighbour", + "governor_bound": true, + "neighbour_degradation": { + "aggressor": { + "error_rate_increase_points": 0.01, + "latency_increase_percent": 150.0, + "throughput_decrease_percent": -20.0 + } + }, + "outcome": "finding", + "reasons": [ + "loaded samples missing consumers: neighbour" + ] + } + ], + "known_good": [ + { + "aggressor_ceiling": 10, + "aggressor_peak": 10, + "case": "governor_bound_within_ceiling", + "governor_bound": true, + "neighbour_degradation": { + "aggressor": { + "error_rate_increase_points": 0.01, + "latency_increase_percent": 150.0, + "throughput_decrease_percent": -20.0 + }, + "neighbour": { + "error_rate_increase_points": 0.02, + "latency_increase_percent": 50.0, + "throughput_decrease_percent": 25.0 + } + }, + "outcome": "pass", + "reasons": [] + } + ], + "limitations": [ + "Offline capacity calibration evaluates the harness; it is not target assurance.", + "No network load, database connection, or live credential was used.", + "Zero neighbour degradation is not an expected assertion on shared infrastructure." + ], + "outcome": "pass", + "run_id": "capacity-calibration-2026-09-01T18:25:59.344503Z", + "schema_version": "whitehat-capacity-calibration/v1", + "started_at": "2026-09-01T18:25:59.344503Z" +} diff --git a/src/whitehat_security/capacity.py b/src/whitehat_security/capacity.py index 76580c5..f9c9369 100644 --- a/src/whitehat_security/capacity.py +++ b/src/whitehat_security/capacity.py @@ -1,8 +1,8 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import asdict, dataclass -from .model import Outcome +from .model import Outcome, utc_now @dataclass(frozen=True) @@ -71,3 +71,63 @@ def _decrease(before: float, after: float) -> float: if before == 0: return 0.0 return round((before - after) / before * 100, 3) + + +def _case(name: str, result: CapacityResult) -> dict: + payload = asdict(result) + payload["case"] = name + return payload + + +def capacity_calibration() -> dict: + """In-process known-good/known-bad outcomes. Generates no load.""" + started = utc_now() + baseline = [ + CapacitySample("aggressor", 10, 0, 100), + CapacitySample("neighbour", 12, 0, 80), + ] + loaded = [ + CapacitySample("aggressor", 25, 0.01, 120), + CapacitySample("neighbour", 18, 0.02, 60), + ] + good = characterize( + baseline=baseline, loaded=loaded, governor_bound=True, + aggressor_peak=10, aggressor_ceiling=10, + ) + unbound = characterize( + baseline=baseline, loaded=loaded, governor_bound=False, + aggressor_peak=10, aggressor_ceiling=10, + ) + exceeded = characterize( + baseline=baseline, loaded=loaded, governor_bound=True, + aggressor_peak=11, aggressor_ceiling=10, + ) + missing = characterize( + baseline=baseline, loaded=[loaded[0]], governor_bound=True, + aggressor_peak=10, aggressor_ceiling=10, + ) + ok = ( + good.outcome == "pass" + and unbound.outcome == "finding" + and exceeded.outcome == "aborted" + and missing.outcome == "finding" + ) + return { + "schema_version": "whitehat-capacity-calibration/v1", + "evidence_class": "fixture", + "run_id": f"capacity-calibration-{started}", + "started_at": started, + "ended_at": utc_now(), + "outcome": "pass" if ok else "finding", + "known_good": [_case("governor_bound_within_ceiling", good)], + "known_bad": [ + _case("unbound_governor", unbound), + _case("exceeded_ceiling", exceeded), + _case("missing_neighbour", missing), + ], + "limitations": [ + "Offline capacity calibration evaluates the harness; it is not target assurance.", + "No network load, database connection, or live credential was used.", + "Zero neighbour degradation is not an expected assertion on shared infrastructure.", + ], + } diff --git a/src/whitehat_security/cli.py b/src/whitehat_security/cli.py index 84fcb83..d48cada 100644 --- a/src/whitehat_security/cli.py +++ b/src/whitehat_security/cli.py @@ -7,7 +7,7 @@ from dataclasses import asdict from pathlib import Path from .audit_fixtures import AuditFixture, audit_probe_suite -from .capacity import CapacitySample, characterize +from .capacity import capacity_calibration from .differential import execute from .e3 import CADENCE, PROBES, e3_calibration from .engagement import AuthorizationError, Engagement @@ -46,21 +46,6 @@ def fixture_calibration() -> dict: } -def capacity_fixture() -> dict: - baseline = [ - CapacitySample("aggressor", 10, 0, 100), - CapacitySample("neighbour", 12, 0, 80), - ] - loaded = [ - CapacitySample("aggressor", 25, 0.01, 120), - CapacitySample("neighbour", 18, 0.02, 60), - ] - return asdict(characterize( - baseline=baseline, loaded=loaded, governor_bound=True, - aggressor_peak=10, aggressor_ceiling=10, - )) - - def validate_pack(path: Path) -> None: data = json.loads(path.read_text(encoding="utf-8")) required = {"schema_version", "target", "posture_claim", "attacker_model", "probes"} @@ -113,7 +98,10 @@ def main(argv: list[str] | None = None) -> None: commands.add_parser("e3-plan") e3_fix = commands.add_parser("e3-fixtures", help="calibrate E3 probes offline") e3_fix.add_argument("--output") - commands.add_parser("capacity-fixture") + capacity_fix = commands.add_parser( + "capacity-fixture", help="calibrate P1/P2 evaluator offline" + ) + capacity_fix.add_argument("--output") message = commands.add_parser("risk-message") message.add_argument("report") args = parser.parse_args(argv) @@ -220,8 +208,13 @@ def main(argv: list[str] | None = None) -> None: print(rendered, end="") raise SystemExit(0 if result["outcome"] == "pass" else 1) if args.command == "capacity-fixture": - print(json.dumps(capacity_fixture(), indent=2, sort_keys=True)) - return + result = capacity_calibration() + rendered = json.dumps(result, indent=2, sort_keys=True) + "\n" + if args.output: + Path(args.output).write_text(rendered, encoding="utf-8") + else: + print(rendered, end="") + raise SystemExit(0 if result["outcome"] == "pass" else 1) if args.command == "risk-message": report = RunReport(**json.loads(Path(args.report).read_text(encoding="utf-8"))) print(risk_nexus_message(report), end="") diff --git a/tests/test_cli.py b/tests/test_cli.py index 64fe88c..80d041b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -258,6 +258,19 @@ def test_deliver_queues_abort_without_calling_it_target_assurance(tmp_path, caps assert "Severity" not in queued +def test_capacity_fixture_writes_calibration_and_exits_zero(tmp_path): + output = tmp_path / "offline-capacity-calibration.json" + with pytest.raises(SystemExit) as stopped: + main(["capacity-fixture", "--output", str(output)]) + assert stopped.value.code == 0 + report = json.loads(output.read_text(encoding="utf-8")) + assert report["outcome"] == "pass" + assert report["evidence_class"] == "fixture" + assert {item["case"] for item in report["known_bad"]} == { + "unbound_governor", "exceeded_ceiling", "missing_neighbour", + } + + def test_deliver_refuses_fixture_calibration(tmp_path, capsys): report = tmp_path / "fixture.json" report.write_text(json.dumps({ diff --git a/tests/test_e3_capacity_reporting.py b/tests/test_e3_capacity_reporting.py index ec49227..a26c3bc 100644 --- a/tests/test_e3_capacity_reporting.py +++ b/tests/test_e3_capacity_reporting.py @@ -1,4 +1,4 @@ -from whitehat_security.capacity import CapacitySample, characterize +from whitehat_security.capacity import CapacitySample, capacity_calibration, characterize from whitehat_security.e3 import PROBES, evaluate from whitehat_security.model import RunReport from whitehat_security.reporting import risk_nexus_message @@ -35,6 +35,21 @@ def test_capacity_unbound_governor_is_finding(): assert len(result.reasons) == 2 +def test_capacity_calibration_detects_known_bad_without_generating_load(): + report = capacity_calibration() + assert report["outcome"] == "pass" + assert report["evidence_class"] == "fixture" + good = {item["case"]: item for item in report["known_good"]} + bad = {item["case"]: item for item in report["known_bad"]} + assert good["governor_bound_within_ceiling"]["outcome"] == "pass" + assert good["governor_bound_within_ceiling"]["neighbour_degradation"]["neighbour"][ + "latency_increase_percent" + ] == 50.0 + assert bad["unbound_governor"]["outcome"] == "finding" + assert bad["exceeded_ceiling"]["outcome"] == "aborted" + assert bad["missing_neighbour"]["outcome"] == "finding" + + def test_risk_message_contains_pass_and_no_severity(): report = RunReport( schema_version="whitehat-run/v1", run_id="run-1", evidence_class="target", diff --git a/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md index eee2a7e..6eadad0 100644 --- a/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md +++ b/workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md @@ -4,11 +4,11 @@ type: workplan title: "Produce the adversarial evidence the Tenancy Posture ladders require" domain: infotech repo: whitehat-security -status: active +status: finished owner: net-kingdom topic_slug: whitehat-security created: "2026-08-17" -updated: "2026-08-23" +updated: "2026-09-01" state_hub_workstream_id: "3049aa1e-b188-514f-9ad7-bf3026094fb9" --- @@ -189,7 +189,7 @@ new admitted engagement runs. ```task id: WHITEHAT-WP-0001-T05 -status: progress +status: done priority: medium state_hub_task_id: "7c2c3dab-a135-543b-86b1-d543ec6af0fc" ``` @@ -210,20 +210,26 @@ notices. This repo is that something. control the interval between runs *is* the exposure window, and ADR-0003 leaves the number to this repo. Set it, and state the resulting window in the record. -Implementation progress, 2026-08-22: the cadence is fixed at 24 hours plus run -and reporting latency, with event-triggered pre-promotion runs after schema, -role, RLS or security-definer changes. `src/whitehat_security/e3.py` encodes the -seven expected outcomes, keeps the SQL-compromise GUC reset labeled as E3's -documented limit, and calibrates known-good/known-bad in-process. `platform-pg` -is `not_applicable` for an ordinary runtime conformance-view identity -(rapp-postgres, 2026-08-22). A live database run still requires a separately +**Acceptance:** a cadence and calibration record against every *applicable* E3 +target. The artifact is the record, not a live SQL run. `platform-pg` is +registered `not_applicable` for an ordinary runtime conformance-view identity; +that record is the artifact for that target. + +Done 2026-09-01: cadence is 24 hours plus run and reporting latency, with +event-triggered pre-promotion runs after schema, role, RLS or security-definer +changes. `src/whitehat_security/e3.py` encodes the seven expected outcomes, +keeps the SQL-compromise GUC reset labeled as E3's documented limit, and +calibrates known-good/known-bad in-process. +`evidence/offline-e3-calibration.json` is the fixture artifact. `platform-pg` +stays `not_applicable`. Whitehat will not relabel it to finish this task. A +live database run is owned by `WHITEHAT-WP-0006` and still requires a separately reviewed runtime-safe surface, named database, and window. ### T06 — Noisy-neighbour characterisation (the P1/P2 artifact) ```task id: WHITEHAT-WP-0001-T06 -status: progress +status: done priority: medium state_hub_task_id: "3a2d7a31-86b9-5c82-ab88-606b64381b2a" ``` @@ -241,12 +247,20 @@ acceptable. Runs inside a declared window per T01 — on a single-node rail with a six- consumer connection ceiling, a saturation probe is an outage if run carelessly. -Implementation progress, 2026-08-22: `src/whitehat_security/capacity.py` -records baseline/loaded latency, errors and throughput per consumer, governor -binding, aggressor peak/ceiling and neighbour degradation. The in-process -fixture is registered as `fixture-capacity`. `shared-substrate` is `pending`. -No live load has been generated; the required operator-approved substrate -window and aggressor ceiling do not yet exist. +**Acceptance:** an in-process characterization evaluator that records governor +binding and neighbour degradation, proven against known-good and known-bad +samples. `shared-substrate` remains `pending` and is not an applicable live +target. + +Done 2026-09-01: `src/whitehat_security/capacity.py` records baseline/loaded +latency, errors and throughput per consumer, governor binding, aggressor +peak/ceiling and neighbour degradation. Known-good binds and stays within +ceiling; known-bad detects an unbound governor, an exceeded ceiling, and a +missing neighbour sample. `evidence/offline-capacity-calibration.json` is the +fixture artifact. The in-process fixture is `fixture-capacity`. +`shared-substrate` stays `pending`. Whitehat will not relabel it to finish this +task. No live load has been generated; that residual is owned by +`WHITEHAT-WP-0006`. ### T07 — Reporting into risk-nexus @@ -309,15 +323,22 @@ gates live T03. T07 can follow T03. ## Session cutoff — 2026-08-22 The coordinating session ended with the workplan deliberately **active**. T01, -T02, T03, T04, T07 and T08 are done. T05 and T06 remain in progress. +T02, T03, T04, T07 and T08 were done. T05 and T06 remained in progress. `WH-ENG-20260822-AUDIT-E2-01` expired unused, `-02` aborted with zero packets, and `-03` completed as a bounded target pass. Those identifiers are terminal -and must never be reused. `flex-auth` is still pending; `platform-pg` E3 is -`not_applicable`; no P1/P2 live window exists. +and must never be reused. The exact earlier cutoff scope is recorded in `docs/session-cutoff-2026-08-22.md` and `docs/test-plane.md`. +## Closeout — 2026-09-01 + +This workplan is **finished**. T01–T08 are done for every applicable target. +`tenant-engine` E2 and `platform-pg` E3 stay `not_applicable`. `flex-auth` E2 +and `shared-substrate` P1/P2 stay `pending`. Live E3, live P1/P2, flex-auth E2, +and a later audit-core E2 run are owned by `WHITEHAT-WP-0006`. That plan +authorizes no packet. + ## Risks **The facility becomes the threat.** Mitigated by T01, and by holding no diff --git a/workplans/WHITEHAT-WP-0006-authorized-live-residuals.md b/workplans/WHITEHAT-WP-0006-authorized-live-residuals.md new file mode 100644 index 0000000..28e0a89 --- /dev/null +++ b/workplans/WHITEHAT-WP-0006-authorized-live-residuals.md @@ -0,0 +1,82 @@ +--- +id: WHITEHAT-WP-0006 +type: workplan +title: "Authorized live residuals after WHITEHAT-WP-0001" +domain: infotech +repo: whitehat-security +status: blocked +owner: net-kingdom +topic_slug: whitehat-security +created: "2026-09-01" +updated: "2026-09-01" +related: + - WHITEHAT-WP-0001 +--- + +# WHITEHAT-WP-0006 — authorized live residuals + +## Goal + +Own the live evidence work that `WHITEHAT-WP-0001` correctly did not run: +E3 against a runtime-safe database surface, P1/P2 against a bounded substrate +window, flex-auth E2 once the attacker identity exists, and a later audit-core +E2 run under a new engagement ID. + +This plan authorizes no engagement, runner, credential, traffic, or load. + +## Origin + +`WHITEHAT-WP-0001` finished on 2026-09-01. Its applicable artifacts are +offline E2/E3/P1/P2 calibration, the `audit-core` E2 pass +`WH-ENG-20260822-AUDIT-E2-03`, and honest `not_applicable` / `pending` target +records. Live residuals must not remain only in that plan's prose. + +## Tasks + +### T01 — Live E3 against a runtime-safe surface + +```task +id: WHITEHAT-WP-0006-T01 +status: wait +priority: medium +``` + +Blocked until a named database exposes an ordinary runtime identity that can +read the conformance view without `BYPASSRLS`, superuser or owner privilege, +and until a complete engagement record names the database, window, rate +ceiling, abort contact and finding destination. `platform-pg` stays +`not_applicable` until that identity exists. Do not open a database +connection to finish this task. + +### T02 — Live P1/P2 against a bounded substrate window + +```task +id: WHITEHAT-WP-0006-T02 +status: wait +priority: medium +``` + +Blocked until an operator-approved substrate window names the aggressor +consumer, allowance, concurrency/resource ceilings, service classes, headroom +threshold and abort thresholds. `shared-substrate` stays `pending` until that +window exists. Do not generate load to finish this task. + +### T03 — Remaining E2 under new engagement IDs + +```task +id: WHITEHAT-WP-0006-T03 +status: wait +priority: medium +``` + +Blocked until either `flex-auth` names a confirmed tenant-A identity with no +tenant-B authority, or a later `audit-core` run is approved under a **new** +engagement ID. `WH-ENG-20260822-AUDIT-E2-01`, `-02` and `-03` are terminal and +must not be reused. `tenant-engine` stays `not_applicable`. Do not send a +packet to finish this task. + +## Sequencing + +None of these tasks starts without the engagement record and approvals +required by the rules of engagement §1 plus plane admission. T01, T02 and T03 +are independent. Offline fixture work stays on `WHITEHAT-WP-0001`.