State the harness scope and calibrate E4 and R4 offline.

SCOPE.md now matches the commands that run. E4 and R4 fail known-bad fixtures in process, and the cadence register schedules nothing.

Assistant: grok
Assistant-Session: 01a10450-dda4-7253-91a9-b8dccb2f0173
This commit is contained in:
tegwick 2026-10-04 08:54:40 +02:00
parent 314e2a8bf2
commit 7a2373ae4c
24 changed files with 989 additions and 58 deletions

View file

@ -11,6 +11,9 @@ 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 e4-fixtures --output evidence/offline-e4-calibration.json
PYTHONPATH=src python3 -m whitehat_security.cli r4-fixtures --output evidence/offline-r4-calibration.json
PYTHONPATH=src python3 -m whitehat_security.cli cadence > evidence/probe-cadence.json
PYTHONPATH=src python3 -m whitehat_security.cli capacity-fixture --output evidence/offline-capacity-calibration.json
PYTHONPATH=src python3 -m whitehat_security.cli asm-fixtures --test-id T-01 --output evidence/offline-asm-t01-calibration.json
PYTHONPATH=src python3 -m whitehat_security.cli asm-fixtures --test-id T-02 --output evidence/offline-asm-t02-calibration.json

View file

@ -1,12 +1,12 @@
# whitehat-security
NetKingdom's offensive security facility. Automated white hat IT-security,
pen-testing and isolation-probing — pointed at infrastructure we choose,
including our own.
NetKingdom's offensive security facility. It calibrates adversarial probes
offline and admits a live run only under a complete engagement record. No
live target is authorized by the repository itself.
The estate's **adversarial evidence facility**: it attacks our own systems, on
a schedule, to find out whether the security properties they claim are actually
true.
The estate's **adversarial evidence facility**: it can test whether a claimed
boundary holds, and today that capability is the offline harness plus one
terminal historical audit-core E2 pass. It does not run on a schedule.
It exists because a repo testing its own boundary grades its own homework. The
probes most worth having are the ones an author would not think to write.

138
SCOPE.md
View file

@ -2,36 +2,70 @@
## One-liner
`whitehat-security` is NetKingdom's authorization-bound offensive-security
tooling for producing adversarial evidence about security claims.
`whitehat-security` is NetKingdom's authorization-bound harness for adversarial
evidence. It can calibrate probes offline. It can admit a live run only when
a complete engagement record already exists. No live target is authorized.
## Core idea
## What it can do
The repository turns stated security properties into dated, reproducible attack
attempts. It remains separate from the systems it tests, reports findings
without grading their severity, and never treats a passing probe as proof that a
boundary always holds.
- Calibrate E2 differential probes in process, including an audit-core-shaped
pack. `whitehat fixtures` fails a known-bad fixture before it trusts a
known-good pass. It sends no packet.
- Calibrate E3 row-level-security outcomes in process, including the
SQL-compromise case that stays `inconclusive`. `whitehat e3-fixtures` opens
no database.
- Calibrate E4 credential confinement and R4 erasure recovery in process.
`whitehat e4-fixtures` and `whitehat r4-fixtures` project no credential,
open no store, and are not target assurance. No live E4 or R4 target is
registered.
- Calibrate P1/P2 capacity judgments from supplied samples.
`whitehat capacity-fixture` generates no load.
- Calibrate Active Secrets Management Canon T-01 through T-10 in process.
`whitehat asm-fixtures` sends no packet. The T-06 fixture counts simulated
custody calls; it is not an OpenBao client.
- Validate engagement records, target registrations, and probe packs, and
refuse plane admission unless the target is applicable, the approval class
matches, the kill switch is clear, and the engagement is inside its window.
- Render a value-safe run message and queue it as a local outbox file.
`whitehat deliver` does not file the message in `risk-nexus` and does not
assign severity.
- Render a Gate House conformance envelope. `whitehat conformance-message`
does not send it.
- Review a sealed Emergent knowledge package offline with
`tools/offline_emergent_knowledge_review.py`. That tool does not call a
provider and does not re-pin `informed-decision`.
- Keep an `audit-core` E2 runner that can send HTTP only from an admitted
plane pod. The dated engagements that used it are terminal.
## In scope
## What it cannot do
- Attacker models for tenant isolation, credential confinement, noisy-neighbour
behavior and erasure verification.
- Differential probes that compare behavior across controlled tenant contexts.
- Known-bad and known-good fixtures that demonstrate every probe can fail.
- Rules of engagement, target authorization, engagement records, abort
controls and evidence minimization.
- Probe cadence and the resulting assurance/exposure window.
- Delivery of findings and passing-run evidence to `risk-nexus`.
- Run on a schedule. `whitehat cadence` lists every family with
`scheduled: false`. The E3 interval of 24 hours is a declared detection
window, not an operating timer.
- Send a packet, open a database connection, generate load, or lease a live
credential under the current records. `WHITEHAT-WP-0006` and
`WHITEHAT-WP-0008` stay blocked on owner returns.
- Treat a fixture pass, or the terminal audit-core E2 pass, as proof that a
boundary holds.
- File findings, grade severity, disclose them, or fix a target.
- Hold an OpenBao client, a database driver, or a cluster-mutation API.
- Produce E4 or R4 evidence about a real substrate. Those families stop at
the in-process harness.
- Produce identity, authorization, or availability evidence. The attacker
model does not define those axes.
- Admit `tenant-engine` for E2 or `platform-pg` for E3. Both are
`not_applicable`.
## Out of scope
- Fixing defects in target repositories.
- Assigning severity, disclosure policy or remediation deadlines.
- Assigning severity, disclosure policy, or remediation deadlines.
- Replacing mechanical checks owned by a target repository's CI.
- Defining the estate's security model or publishing permanent policy.
- Probing any target without the authorization and engagement records required
by [the rules of engagement](docs/rules-of-engagement.md).
- Blocking build-mode delivery without a separately recorded decision.
- Turning the local outbox into a `risk-nexus` writer.
## Safety invariants
@ -40,56 +74,62 @@ boundary always holds.
and non-networked fixture design may proceed.
- No live target is authorized by this scope document.
- Every live run names its authorization, target, owner, window, technique,
credential lane, rate ceiling, abort contact and finding destination.
credential lane, rate ceiling, abort contact, and finding destination.
- A probe stops at the recorded boundary and never follows an adjacent system.
- Evidence records response shape and counts, never real tenant row values or
credentials.
- Findings leave this repository; repairs do not enter it.
- A probe is trusted only after it has failed a known-bad fixture.
## Relevant when
- A service claims a Tenancy Posture evidence level that requires adversarial
rather than mechanical evidence.
- A boundary must be tested using a leaked runtime credential or hostile tenant
context.
- The estate needs to know when a probe last ran, what attacker it modeled, and
whether it was proven against a known-bad fixture.
rather than mechanical evidence, and an engagement record for that service
already exists or the work is an offline fixture.
- The estate needs the attacker model, the last dated run, and whether the
harness has been shown to fail when the control is absent.
## Not relevant when
- A repository needs unit, schema or provisioning tests for its own code.
- A finding needs triage, severity or disclosure handling; use `risk-nexus`.
- A repository needs unit, schema, or provisioning tests for its own code.
- A finding needs triage, severity, or disclosure handling; use `risk-nexus`.
- Permanent policy needs publication; use `policy-nexus`.
- The desired activity falls outside an approved engagement boundary.
- The desired activity is a live probe and no complete engagement exists.
## Current state
- Repository status: active.
- Layer: Staff (declared in `INTENT.md`, 2026-09-01). `WHITEHAT-IN-0001` is
closed.
- Finished: `WHITEHAT-WP-0001` through `WHITEHAT-WP-0005` and
`WHITEHAT-WP-0007`. Residual live Tenancy Posture evidence is
`WHITEHAT-WP-0006`. Residual live ASM is `WHITEHAT-WP-0008`. Both stay
`blocked` after the 2026-10-04 recheck and the owner returns the same day.
T01 still waits on `rapp-postgres` for an ordinary runtime identity. T02
still waits on an operator substrate window. T03 still waits on
`access-engine` or a new `audit-core` engagement ID. Live ASM still waits
on a surface owner, with T-06 on `approval-engine` and a consuming PEP.
No packet is authorized.
- Layer: Staff (declared in `INTENT.md`, 2026-09-01). `conformance_state`
stays `blocked-clean`. `WHITEHAT-IN-0001` is closed.
- The capability statement above is the 2026-10-04 scope assessment. The
comparison against `INTENT.md` is
[`history/2026-10-04T0649Z-scope-against-intent.md`](history/2026-10-04T0649Z-scope-against-intent.md).
`WHITEHAT-WP-0011` added the E4 and R4 calibrations and the cadence
register. It authorizes no packet.
- Finished: `WHITEHAT-WP-0001` through `WHITEHAT-WP-0005`,
`WHITEHAT-WP-0007`, `WHITEHAT-WP-0009`, `WHITEHAT-WP-0010`, and
`WHITEHAT-WP-0011`.
- Residual live Tenancy Posture evidence is `WHITEHAT-WP-0006`. Residual
live ASM is `WHITEHAT-WP-0008`. Both stay `blocked` after the 2026-10-04
owner returns. T01 still waits on `rapp-postgres` for an ordinary runtime
identity. T02 still waits on an operator substrate window. T03 still waits
on `access-engine` or a new `audit-core` engagement ID. Live ASM still
waits on a surface owner, with T-06 on `approval-engine` and a consuming
PEP. No packet is authorized.
- `WHITEHAT-WP-0007` triaged Gate House ASM T-01…T-10. Live `asm-tNN`
registrations remain `pending`. In-process `fixture-asm-t01`…`t10`
calibrations exist. That work authorized no live probe.
- `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
E2 and `shared-substrate` P1/P2 stay `pending`. Offline E2, E3, E4, R4, 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.
- `WHITEHAT-WP-0009` is finished. The 0.2.1 record shows WH-KNOW-001 and
WH-KNOW-002. A local 0.2.3 retest did not reproduce them. `risk-nexus`
holds open RISK-F-0013, RISK-F-0014 and RISK-F-0015. `emergent-security`
holds open RISK-F-0013, RISK-F-0014, and RISK-F-0015. `emergent-security`
commit `19e7feb` accepts the scoped review and the two library
remediations, and leaves provider evidence and the `informed-decision`
pin reconciliation in blocked EMERGENT-WP-0002. This plan authorizes no
@ -106,9 +146,9 @@ boundary always holds.
## Relationships
- Owner and security canon: `net-kingdom`.
- Finding intake, severity and disclosure: `risk-nexus`.
- Finding intake, severity, and disclosure: `risk-nexus`.
- Permanent publication: `policy-nexus`.
- Initial proposed target owners: `tenant-engine`, `audit-core` and
- Initial proposed target owners: `tenant-engine`, `audit-core`, and
`flex-auth`.
## Getting oriented
@ -116,11 +156,15 @@ boundary always holds.
1. Read [`INTENT.md`](INTENT.md) for the durable purpose and ownership model.
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)
3. Read [`history/2026-10-04T0649Z-scope-against-intent.md`](history/2026-10-04T0649Z-scope-against-intent.md)
for the capability gaps against that intent.
4. Read [`WHITEHAT-WP-0001`](workplans/WHITEHAT-WP-0001-cross-tenant-evidence.md)
for the finished applicable-target artifacts,
[`WHITEHAT-WP-0006`](workplans/WHITEHAT-WP-0006-authorized-live-residuals.md)
for unauthorized live Tenancy Posture residuals, and
for unauthorized live Tenancy Posture residuals,
[`WHITEHAT-WP-0007`](workplans/WHITEHAT-WP-0007-asm-t01-t10-triage.md)
for finished ASM T-01…T-10 triage, and
for finished ASM T-01…T-10 triage,
[`WHITEHAT-WP-0008`](workplans/WHITEHAT-WP-0008-live-asm-residuals.md)
for unauthorized live ASM residuals.
for unauthorized live ASM residuals, and
[`WHITEHAT-WP-0011`](workplans/WHITEHAT-WP-0011-scope-capability-gaps.md)
for the offline E4, R4, and cadence work.

View file

@ -165,6 +165,9 @@ credential and substrate confinement.
A platform, migration, break-glass or tenant B credential; control of the
credential issuer; or permission to alter routing/provisioning state.
Executable calibration is `whitehat e4-fixtures`. It is in-process, projects
no credential, and is not target assurance. No live E4 target is registered.
## 6. Shared P1/P2 capacity — bounded co-resident
This model tests capacity assurance, not the truth of the P1/P2 topology.
@ -232,6 +235,10 @@ outcome is inconclusive rather than pass.
targets R2. The facility does not erase the evidence service's retained facts
to satisfy a fleet-wide aspiration.
Executable calibration is `whitehat r4-fixtures`. It is in-process, opens no
store, and is not target assurance. An incomplete copy inventory stays
inconclusive. No live R4 target is registered.
## 8. Axes outside this first model
Identity (`I`), authorization (`A`) and availability (`V`) have evidence

View file

@ -4,7 +4,9 @@ The E3 detection interval is **24 hours**, so the declared maximum exposure
window is **24 hours plus run and reporting latency**. A run is also required
before deployment promotion following any schema migration, RLS policy change,
role/grant change, security-definer function change, or posture-mechanism
change. The event-triggered run does not reset the daily schedule.
change. The event-triggered run does not reset the daily schedule. Nothing in this
repository schedules that run. `whitehat cadence` is the register, and every
family in it has `scheduled: false`.
This is a detection control, not prevention. A migration can introduce an
unprotected table between runs; the interval states how long that condition

View file

@ -1,11 +1,12 @@
# Evidence
This directory stores sanitized run artifacts. `offline-calibration.json`,
`offline-e3-calibration.json`, `offline-capacity-calibration.json`, and
`offline-e3-calibration.json`, `offline-e4-calibration.json`,
`offline-r4-calibration.json`, `offline-capacity-calibration.json`, and
`offline-asm-t01-calibration.json` … `offline-asm-t10-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`
assurance. `probe-cadence.json` is the unscheduled cadence register. `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

View file

@ -0,0 +1,58 @@
{
"ended_at": "2026-10-04T06:54:09.471305Z",
"evidence_class": "fixture",
"known_bad": [
{
"outcome": "finding",
"probe_id": "present-to-other-substrate",
"reason": "tenant A credential addressed tenant B substrate"
},
{
"outcome": "finding",
"probe_id": "alternate-database",
"reason": "tenant A credential addressed tenant B substrate"
},
{
"outcome": "finding",
"probe_id": "alternate-schema",
"reason": "tenant A credential addressed tenant B substrate"
},
{
"outcome": "aborted",
"probe_id": "refresh-outside-authority",
"reason": "credential refresh outside recorded authority is a probe-control incident"
}
],
"known_bad_failed_first": true,
"known_good": [
{
"outcome": "pass",
"probe_id": "present-to-other-substrate",
"reason": "credential did not authenticate to or address tenant B"
},
{
"outcome": "pass",
"probe_id": "alternate-database",
"reason": "credential did not authenticate to or address tenant B"
},
{
"outcome": "pass",
"probe_id": "alternate-schema",
"reason": "credential did not authenticate to or address tenant B"
},
{
"outcome": "pass",
"probe_id": "reachability-only",
"reason": "reachability alone is not an E4 failure"
}
],
"limitations": [
"Offline E4 calibration evaluates the harness; it is not target assurance.",
"No credential was leased, and no network or database call was made.",
"No live E4 target is registered. Reachability alone is not a finding."
],
"outcome": "pass",
"run_id": "e4-calibration-2026-10-04T06:54:09.471214Z",
"schema_version": "whitehat-e4-calibration/v1",
"started_at": "2026-10-04T06:54:09.471214Z"
}

View file

@ -0,0 +1,75 @@
{
"ended_at": "2026-10-04T06:54:09.672396Z",
"evidence_class": "fixture",
"inconclusive": [
{
"outcome": "inconclusive",
"probe_id": "inventory-incomplete",
"reason": "declared copies were not all exercised",
"route": "R4-A"
},
{
"outcome": "inconclusive",
"probe_id": "before-horizon",
"reason": "recovery before the horizon is not an R4-A result",
"route": "R4-A"
},
{
"outcome": "inconclusive",
"probe_id": "key-not-destroyed",
"reason": "key destruction alone is not a pass, and destruction was not recorded",
"route": "R4-B"
}
],
"known_bad": [
{
"outcome": "finding",
"probe_id": "horizon-recovered",
"reason": "fixture content recovered after the erasure horizon",
"route": "R4-A"
},
{
"outcome": "finding",
"probe_id": "ciphertext-readable",
"reason": "erased fixture remains readable or confirmable",
"route": "R4-B"
},
{
"outcome": "finding",
"probe_id": "usable-key-survives",
"reason": "erased fixture remains readable or confirmable",
"route": "R4-B"
},
{
"outcome": "finding",
"probe_id": "low-entropy-oracle",
"reason": "erased fixture remains readable or confirmable",
"route": "R4-B"
}
],
"known_bad_failed_first": true,
"known_good": [
{
"outcome": "pass",
"probe_id": "horizon-clear",
"reason": "no fixture content recovered from the declared copies",
"route": "R4-A"
},
{
"outcome": "pass",
"probe_id": "key-destroyed-clear",
"reason": "retained ciphertext is unreadable and no confirmation oracle remains",
"route": "R4-B"
}
],
"limitations": [
"Offline R4 calibration evaluates the harness; it is not target assurance.",
"No backup, replica, key, or ciphertext was accessed.",
"audit-core is not an R4 target. No live R4 target is registered.",
"An incomplete copy inventory stays inconclusive and is not a pass."
],
"outcome": "pass",
"run_id": "r4-calibration-2026-10-04T06:54:09.672295Z",
"schema_version": "whitehat-r4-calibration/v1",
"started_at": "2026-10-04T06:54:09.672295Z"
}

View file

@ -0,0 +1,54 @@
{
"families": [
{
"declared_interval": null,
"executable": "whitehat fixtures",
"exposure_window": "Per approved engagement window only. The dated audit-core pass is terminal. No standing schedule replaces it.",
"family": "E2",
"live_owner": "WHITEHAT-WP-0006-T03",
"scheduled": false
},
{
"declared_interval": "24h",
"executable": "whitehat e3-fixtures",
"exposure_window": "24h plus run and reporting latency, once a runtime-safe surface exists. The interval is not operating.",
"family": "E3",
"live_owner": "WHITEHAT-WP-0006-T01",
"scheduled": false
},
{
"declared_interval": null,
"executable": "whitehat e4-fixtures",
"exposure_window": "No live target and no schedule, so no detection window.",
"family": "E4",
"live_owner": null,
"scheduled": false
},
{
"declared_interval": null,
"executable": "whitehat capacity-fixture",
"exposure_window": "An operator window only. The harness generates no load.",
"family": "P1/P2",
"live_owner": "WHITEHAT-WP-0006-T02",
"scheduled": false
},
{
"declared_interval": null,
"executable": "whitehat r4-fixtures",
"exposure_window": "No live target and no schedule, so no detection window.",
"family": "R4",
"live_owner": null,
"scheduled": false
},
{
"declared_interval": null,
"executable": "whitehat asm-fixtures",
"exposure_window": "Per approved engagement for one named Canon test_id.",
"family": "ASM T-01..T-10",
"live_owner": "WHITEHAT-WP-0008-T01",
"scheduled": false
}
],
"scheduled_live_runs": false,
"schema_version": "whitehat-cadence/v1"
}

View file

@ -0,0 +1,131 @@
# Scope against intent — 2026-10-04T0649Z
Assessment of `SCOPE.md` against `INTENT.md` after reading the harness, the
target catalog, and the rules of engagement. This file records the gaps. It
does not authorize a packet, a database connection, or a load run.
`conformance_state` stays `blocked-clean`.
## Method
Compared each item under "What it owns" in `INTENT.md` with the code that can
run today: `src/whitehat_security/`, `runners/audit_core_e2.py`, `targets/`,
`whitehat` commands, and the blocked workplans `WHITEHAT-WP-0006` and
`WHITEHAT-WP-0008`. A capability counts only when a command or runner exists
and its limit is explicit. A prose attacker-model section is not a probe.
## What the repository can actually do
| Facility | Present | Limit |
| --- | --- | --- |
| E2 differential harness | `whitehat fixtures`, generic and audit-shaped fixtures | In process. Not a live target. |
| E2 live runner | `runners/audit_core_e2.py` | Can send HTTP from an admitted pod. Engagements `-01`, `-02`, and `-03` are terminal. |
| E3 evaluator and 24h window | `whitehat e3-fixtures`, `whitehat e3-plan` | No database driver. Nothing schedules the 24h window. |
| E4 confinement | `whitehat e4-fixtures`, `targets/fixture-e4.json` | In process. No live target. Projects no credential. |
| P1/P2 capacity judgment | `whitehat capacity-fixture` | Judges supplied samples. Generates no load. |
| R4 erasure | `whitehat r4-fixtures`, `targets/fixture-r4.json` | In process. No store and no key. Incomplete inventory stays inconclusive. |
| ASM T-01…T-10 | `whitehat asm-fixtures` | In process. Live registrations stay `pending`. |
| Plane admission | `whitehat admit-plane` | Fail-closed. Fixture classes cannot use a live environment. |
| Finding delivery | `whitehat deliver` | Writes `outbox/`. Does not file in `risk-nexus`. |
| Cadence | `whitehat cadence` | Every family has `scheduled: false`. |
| Knowledge review | `tools/offline_emergent_knowledge_review.py` | Sealed-package offline review. Not a tenancy probe and not a provider call. |
| Custody | default broker raises; receipt broker projects handles | No OpenBao client and no credential value in evidence. |
The previous `SCOPE.md` listed attacker models, cadence, and finding delivery
as in-scope capabilities without saying which of them run. The README said the
facility attacks systems on a schedule. Both overclaimed. `SCOPE.md` now
states the table above.
## Intent item by item
### Adversarial evidence artifacts
Intent owns E2, E3, E4, P1/P2, and R4.
- E2 is partial. The harness and one terminal audit-core pass exist.
`flex-auth` is pending. `tenant-engine` is `not_applicable`. Further live
E2 waits on `WHITEHAT-WP-0006-T03`.
- E3 is partial. The evaluator and the declared 24h window exist. No
runtime-safe database exists. `platform-pg` is `not_applicable`. Live E3
waits on `WHITEHAT-WP-0006-T01`. `rapp-postgres` confirmed on 2026-10-04
that the ordinary runtime identity still lacks SELECT on the conformance
view.
- E4 was a gap. The attacker model described it and no probe existed, so it
could not fail a known-bad fixture. `WHITEHAT-WP-0011` adds that fixture.
There is still no live E4 target, which is an honest stop rather than a
missing harness.
- P1/P2 is partial. The sample evaluator exists and generates no load. A live
window waits on an operator record, `WHITEHAT-WP-0006-T02`. The custodian
confirmed that record does not exist.
- R4 was a gap of the same kind as E4. The fixture now exists. `audit-core`
remains not an R4 target. No live R4 target is registered.
### Attacker model
Met for E2, E3, E4, P1/P2, and R4 in `docs/attacker-model.md`. Identity,
authorization, and availability are explicitly outside that model. The model
is not itself evidence.
### Cadence
Partial, and this was the sharpest overclaim. Intent says the interval
between runs is the exposure window for a detection control. Only E3 names an
interval, and no process runs it. The cadence register now says so for every
family. Building a scheduler would either send packets or pretend to. Neither
belongs in this repository while the live plans are blocked.
### Differential testing
Met for the offline E2 harness. It is not running against a live pair of
tenant contexts.
### Target selection and authorization
Met as a control. The plane refuses `not_applicable` and `pending` targets,
retired engagement IDs, a missing owner acknowledgement, and an engaged kill
switch. The control is doing its job by refusing the live residuals.
### Rules of engagement
Met. Version 0.2 is accepted and authorizes no live target by itself.
### Findings delivery
Partial by design. The repository can render a factual message and queue it.
`risk-nexus` files, grades, and discloses. A direct writer here would cross
that boundary. The gap is documentary: delivery must not be described as
filing.
### Staff boundary
Met. There is no database driver, no OpenBao client, and no cluster-mutation
API. The ASM fixture's custody counter is local. The default custody broker
raises before a live custody call.
## Most relevant gaps
1. **Live evidence is still absent for the claims that matter.** E3, the
remaining E2 target, P1/P2, and ASM T-01…T-10 have harnesses and no
authorized run. Owner replies on 2026-10-04 confirmed the missing
identity, substrate window, engagement, and consuming PEP. Those gaps
stay on `WHITEHAT-WP-0006` and `WHITEHAT-WP-0008`. This assessment does
not reopen them and cannot close them from this repository.
2. **E4 and R4 had no probe that could fail.** Intent lists them as owned
evidence, and the repository's own rule is that a probe which has never
failed a known-bad fixture is not evidence. That gap is local and is the
one this repository can close without a packet. `WHITEHAT-WP-0011` does
that. The calibrations are harness evidence only.
3. **The scope document described a scheduled attacking facility.** The
runnable surface is an offline harness, a fail-closed plane, a local
outbox, and one terminal historical pass. `SCOPE.md`, the README lead,
and `whitehat cadence` now say that. No family is scheduled.
Gaps that are real and are not addressed here: identity, authorization, and
availability axes; a `risk-nexus` filing client; a live E4 or R4 target; and
any change to `conformance_state`.
## Resulting work
`WHITEHAT-WP-0011` records this assessment, rewrites the scope to the
capability table, and adds the E4 fixture, the R4 fixture, and the unscheduled
cadence register. A pass in those fixtures means the harness noticed the
known-bad cases. It does not mean a tenant boundary held.

View file

@ -16,7 +16,8 @@
"minItems": 1,
"items": {"enum": [
"fixture-e2", "live-e2", "fixture-e3", "e3",
"fixture-capacity", "capacity", "fixture-asm", "asm"
"fixture-capacity", "capacity", "fixture-asm", "asm",
"fixture-e4", "fixture-r4"
]}
},
"routes": {"type": "array", "items": {"type": "string"}},

View file

@ -0,0 +1,72 @@
"""Declared detection windows. Nothing in this register starts a run."""
from __future__ import annotations
def register() -> dict:
"""Return the probe cadence this repository can actually stand behind.
``scheduled`` is false for every family. A declared interval is the
exposure window that would apply once a live run exists. It is not a
claim that a scheduler is running.
"""
return {
"schema_version": "whitehat-cadence/v1",
"scheduled_live_runs": False,
"families": [
{
"family": "E2",
"executable": "whitehat fixtures",
"declared_interval": None,
"scheduled": False,
"exposure_window": (
"Per approved engagement window only. The dated audit-core "
"pass is terminal. No standing schedule replaces it."
),
"live_owner": "WHITEHAT-WP-0006-T03",
},
{
"family": "E3",
"executable": "whitehat e3-fixtures",
"declared_interval": "24h",
"scheduled": False,
"exposure_window": (
"24h plus run and reporting latency, once a runtime-safe "
"surface exists. The interval is not operating."
),
"live_owner": "WHITEHAT-WP-0006-T01",
},
{
"family": "E4",
"executable": "whitehat e4-fixtures",
"declared_interval": None,
"scheduled": False,
"exposure_window": "No live target and no schedule, so no detection window.",
"live_owner": None,
},
{
"family": "P1/P2",
"executable": "whitehat capacity-fixture",
"declared_interval": None,
"scheduled": False,
"exposure_window": "An operator window only. The harness generates no load.",
"live_owner": "WHITEHAT-WP-0006-T02",
},
{
"family": "R4",
"executable": "whitehat r4-fixtures",
"declared_interval": None,
"scheduled": False,
"exposure_window": "No live target and no schedule, so no detection window.",
"live_owner": None,
},
{
"family": "ASM T-01..T-10",
"executable": "whitehat asm-fixtures",
"declared_interval": None,
"scheduled": False,
"exposure_window": "Per approved engagement for one named Canon test_id.",
"live_owner": "WHITEHAT-WP-0008-T01",
},
],
}

View file

@ -10,7 +10,10 @@ from .audit_fixtures import AuditFixture, audit_probe_suite
from .capacity import capacity_calibration
from .differential import execute
from .asm import asm_calibration
from .cadence import register as cadence_register
from .e3 import CADENCE, PROBES, e3_calibration
from .e4 import e4_calibration
from .r4 import r4_calibration
from .engagement import AuthorizationError, Engagement
from .fixtures import FixtureService, probe_suite
from .model import RunReport, utc_now
@ -97,8 +100,13 @@ def main(argv: list[str] | None = None) -> None:
deliver.add_argument("report")
deliver.add_argument("--outbox", default="outbox")
commands.add_parser("e3-plan")
commands.add_parser("cadence", help="print the unscheduled cadence register")
e3_fix = commands.add_parser("e3-fixtures", help="calibrate E3 probes offline")
e3_fix.add_argument("--output")
e4_fix = commands.add_parser("e4-fixtures", help="calibrate E4 probes offline")
e4_fix.add_argument("--output")
r4_fix = commands.add_parser("r4-fixtures", help="calibrate R4 probes offline")
r4_fix.add_argument("--output")
capacity_fix = commands.add_parser(
"capacity-fixture", help="calibrate P1/P2 evaluator offline"
)
@ -213,6 +221,25 @@ def main(argv: list[str] | None = None) -> None:
print(json.dumps({"cadence": CADENCE, "probes": [asdict(probe) for probe in PROBES]},
indent=2, sort_keys=True))
return
if args.command == "cadence":
print(json.dumps(cadence_register(), indent=2, sort_keys=True))
return
if args.command == "e4-fixtures":
result = e4_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 == "r4-fixtures":
result = r4_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 == "e3-fixtures":
result = e3_calibration()
rendered = json.dumps(result, indent=2, sort_keys=True) + "\n"

View file

@ -0,0 +1,92 @@
from __future__ import annotations
from dataclasses import dataclass
from .model import Outcome, utc_now
@dataclass(frozen=True)
class E4Observation:
"""In-process observation. No credential value and no network call."""
probe_id: str
authenticated: bool = False
read_other: bool = False
wrote_other: bool = False
reachable_only: bool = False
refreshed_outside_authority: bool = False
@dataclass(frozen=True)
class E4Result:
probe_id: str
outcome: Outcome
reason: str
def evaluate(observation: E4Observation) -> E4Result:
"""Judge one E4 observation.
Reachability without authentication is not a finding. Refreshing the
credential outside its recorded authority aborts the probe; it is not
target evidence.
"""
if observation.refreshed_outside_authority:
return E4Result(
observation.probe_id, "aborted",
"credential refresh outside recorded authority is a probe-control incident",
)
if observation.authenticated or observation.read_other or observation.wrote_other:
return E4Result(
observation.probe_id, "finding",
"tenant A credential addressed tenant B substrate",
)
if observation.reachable_only:
return E4Result(
observation.probe_id, "pass",
"reachability alone is not an E4 failure",
)
return E4Result(
observation.probe_id, "pass",
"credential did not authenticate to or address tenant B",
)
def e4_calibration() -> dict:
"""Known-bad must fail before a known-good pass is trusted. No network."""
started = utc_now()
known_bad = [
evaluate(E4Observation("present-to-other-substrate", authenticated=True)),
evaluate(E4Observation("alternate-database", read_other=True)),
evaluate(E4Observation("alternate-schema", wrote_other=True)),
evaluate(E4Observation("refresh-outside-authority", refreshed_outside_authority=True)),
]
bad_held = (
known_bad[0].outcome == "finding"
and known_bad[1].outcome == "finding"
and known_bad[2].outcome == "finding"
and known_bad[3].outcome == "aborted"
)
known_good = [
evaluate(E4Observation("present-to-other-substrate")),
evaluate(E4Observation("alternate-database")),
evaluate(E4Observation("alternate-schema")),
evaluate(E4Observation("reachability-only", reachable_only=True)),
] if bad_held else []
good_held = all(result.outcome == "pass" for result in known_good)
return {
"schema_version": "whitehat-e4-calibration/v1",
"evidence_class": "fixture",
"run_id": f"e4-calibration-{started}",
"started_at": started,
"ended_at": utc_now(),
"outcome": "pass" if bad_held and good_held else "finding",
"known_bad_failed_first": bad_held,
"known_good": [result.__dict__ for result in known_good],
"known_bad": [result.__dict__ for result in known_bad],
"limitations": [
"Offline E4 calibration evaluates the harness; it is not target assurance.",
"No credential was leased, and no network or database call was made.",
"No live E4 target is registered. Reachability alone is not a finding.",
],
}

View file

@ -28,9 +28,12 @@ APPROVAL_CLASSES = {
"capacity": frozenset({"p1-noisy-neighbour", "p2-noisy-neighbour"}),
"fixture-asm": frozenset({"asm-assurance"}),
"asm": frozenset({"asm-assurance"}),
"fixture-e4": frozenset({"e4-confinement"}),
"fixture-r4": frozenset({"r4-erasure"}),
}
FIXTURE_CLASSES = frozenset({
"fixture-e2", "fixture-e3", "fixture-capacity", "fixture-asm",
"fixture-e4", "fixture-r4",
})

128
src/whitehat_security/r4.py Normal file
View file

@ -0,0 +1,128 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal
from .model import Outcome, utc_now
Route = Literal["R4-A", "R4-B"]
@dataclass(frozen=True)
class R4Case:
"""Synthetic erasure observation. No storage client and no key material."""
probe_id: str
route: Route
horizon_elapsed: bool = False
key_destroyed: bool = False
inventory_complete: bool = False
recovered: bool = False
readable: bool = False
usable_key_survives: bool = False
low_entropy_oracle: bool = False
@dataclass(frozen=True)
class R4Result:
probe_id: str
route: Route
outcome: Outcome
reason: str
def evaluate(case: R4Case) -> R4Result:
"""Judge one R4 case. An incomplete inventory is inconclusive, not a pass."""
if not case.inventory_complete:
return R4Result(
case.probe_id, case.route, "inconclusive",
"declared copies were not all exercised",
)
if case.route == "R4-A":
if not case.horizon_elapsed:
return R4Result(
case.probe_id, case.route, "inconclusive",
"recovery before the horizon is not an R4-A result",
)
if case.recovered:
return R4Result(
case.probe_id, case.route, "finding",
"fixture content recovered after the erasure horizon",
)
return R4Result(
case.probe_id, case.route, "pass",
"no fixture content recovered from the declared copies",
)
if not case.key_destroyed:
return R4Result(
case.probe_id, case.route, "inconclusive",
"key destruction alone is not a pass, and destruction was not recorded",
)
if case.readable or case.usable_key_survives or case.low_entropy_oracle:
return R4Result(
case.probe_id, case.route, "finding",
"erased fixture remains readable or confirmable",
)
return R4Result(
case.probe_id, case.route, "pass",
"retained ciphertext is unreadable and no confirmation oracle remains",
)
def r4_calibration() -> dict:
"""Known-bad must fail before a known-good pass is trusted. No storage."""
started = utc_now()
known_bad = [
evaluate(R4Case(
"horizon-recovered", "R4-A",
horizon_elapsed=True, inventory_complete=True, recovered=True,
)),
evaluate(R4Case(
"ciphertext-readable", "R4-B",
key_destroyed=True, inventory_complete=True, readable=True,
)),
evaluate(R4Case(
"usable-key-survives", "R4-B",
key_destroyed=True, inventory_complete=True, usable_key_survives=True,
)),
evaluate(R4Case(
"low-entropy-oracle", "R4-B",
key_destroyed=True, inventory_complete=True, low_entropy_oracle=True,
)),
]
inconclusive = [
evaluate(R4Case("inventory-incomplete", "R4-A", horizon_elapsed=True)),
evaluate(R4Case("before-horizon", "R4-A", inventory_complete=True)),
evaluate(R4Case("key-not-destroyed", "R4-B", inventory_complete=True)),
]
bad_held = all(result.outcome == "finding" for result in known_bad)
limits_held = all(result.outcome == "inconclusive" for result in inconclusive)
known_good = [
evaluate(R4Case(
"horizon-clear", "R4-A",
horizon_elapsed=True, inventory_complete=True,
)),
evaluate(R4Case(
"key-destroyed-clear", "R4-B",
key_destroyed=True, inventory_complete=True,
)),
] if bad_held and limits_held else []
good_held = all(result.outcome == "pass" for result in known_good)
return {
"schema_version": "whitehat-r4-calibration/v1",
"evidence_class": "fixture",
"run_id": f"r4-calibration-{started}",
"started_at": started,
"ended_at": utc_now(),
"outcome": "pass" if bad_held and limits_held and good_held else "finding",
"known_bad_failed_first": bad_held,
"known_good": [result.__dict__ for result in known_good],
"known_bad": [result.__dict__ for result in known_bad],
"inconclusive": [result.__dict__ for result in inconclusive],
"limitations": [
"Offline R4 calibration evaluates the harness; it is not target assurance.",
"No backup, replica, key, or ciphertext was accessed.",
"audit-core is not an R4 target. No live R4 target is registered.",
"An incomplete copy inventory stays inconclusive and is not a pass.",
],
}

View file

@ -45,6 +45,12 @@ def load_registration(path: str | Path) -> dict[str, Any]:
raise AuthorizationError(f"{path}: E3 registration must project one runtime identity")
if classes & {"fixture-capacity", "capacity"} and count not in {0, 1}:
raise AuthorizationError(f"{path}: capacity registration projects at most one aggressor identity")
if classes & {"fixture-e4", "fixture-r4"} and count != 0:
raise AuthorizationError(f"{path}: E4 and R4 fixtures project no credential")
if (classes & {"fixture-e4", "fixture-r4"}) and (
classes & {"live-e2", "e3", "capacity", "asm"}
):
raise AuthorizationError(f"{path}: E4 and R4 fixtures cannot carry a live approval class")
if classes & {"fixture-asm", "asm"} and count not in {0, 1, 2}:
raise AuthorizationError(f"{path}: ASM registration projects at most two identities")
if classes & {"fixture-asm", "asm"} and not data.get("test_id"):

View file

@ -12,6 +12,8 @@ honest applicability record the test plane admits against.
| `fixture-e3` | applicable | In-process E3 evaluator. One runtime identity, no `BYPASSRLS`. |
| `platform-pg` | not_applicable | No ordinary runtime identity can read the conformance view. |
| `fixture-capacity` | applicable | In-process P1/P2 evaluator. Generates no load. |
| `fixture-e4` | applicable | In-process E4 evaluator. Projects no credential. No live E4 target. |
| `fixture-r4` | applicable | In-process R4 evaluator. Opens no store. No live R4 target. |
| `shared-substrate` | pending | Live capacity needs an operator window and aggressor ceiling. |
| `asm-t01` … `asm-t10` | pending | Gate House ASM T-01…T-10 live registrations. Separate `asm` class; not E2/E3/capacity. See `WHITEHAT-WP-0007`. |
| `fixture-asm-t01` … `fixture-asm-t10` | applicable | In-process ASM known-bad/known-good harnesses. Offline only. |

35
targets/fixture-e4.json Normal file
View file

@ -0,0 +1,35 @@
{
"schema_version": "whitehat-target/v1",
"target_id": "fixture-e4",
"posture_claim": "E4",
"attacker_model": "leaked tenant-A credential presented to tenant-B substrate",
"applicability": "applicable",
"applicability_reason": "In-process evaluator created by this repository. It projects no credential and makes no network call. No live E4 target is registered.",
"approval_classes": ["fixture-e4"],
"adapter": "src/whitehat_security/e4.py",
"probe_pack": "src/whitehat_security/e4.py",
"known_bad_calibration": "src/whitehat_security/e4.py",
"fixture_lifecycle": {
"create": "in-process observations",
"delete": "drop process state",
"ids_are_synthetic": true
},
"egress": "in-process",
"routes": [
"other-substrate",
"alternate-database",
"alternate-schema",
"reachability",
"credential-refresh"
],
"identities": {
"count": 0,
"role": "none; fixture projects no credential",
"broker_audience": "whitehat-e4/fixture"
},
"abort_telemetry": [
"credential_refresh_outside_authority",
"unexpected_credential_value",
"network_attempt"
]
}

29
targets/fixture-r4.json Normal file
View file

@ -0,0 +1,29 @@
{
"schema_version": "whitehat-target/v1",
"target_id": "fixture-r4",
"posture_claim": "R4",
"attacker_model": "holder of a pre-erasure copy of synthetic fixture data",
"applicability": "applicable",
"applicability_reason": "In-process evaluator created by this repository. It opens no storage client and holds no key. No live R4 target is registered. audit-core is not an R4 target.",
"approval_classes": ["fixture-r4"],
"adapter": "src/whitehat_security/r4.py",
"probe_pack": "src/whitehat_security/r4.py",
"known_bad_calibration": "src/whitehat_security/r4.py",
"fixture_lifecycle": {
"create": "in-process cases",
"delete": "drop process state",
"ids_are_synthetic": true
},
"egress": "in-process",
"routes": ["horizon", "key-destroyed"],
"identities": {
"count": 0,
"role": "none; fixture holds no key",
"broker_audience": "whitehat-r4/fixture"
},
"abort_telemetry": [
"incomplete_copy_inventory",
"recovered_content_retained",
"key_material_present"
]
}

View file

@ -18,7 +18,7 @@ def test_validate_engagement_reports_clean_denial(tmp_path, capsys):
def test_validate_targets_accepts_catalog(capsys):
main(["validate-targets", "targets"])
assert capsys.readouterr().out.startswith("validated 28 target registrations")
assert capsys.readouterr().out.startswith("validated 30 target registrations")
def test_kill_switch_is_clear_by_default(capsys):

View file

@ -0,0 +1,47 @@
from whitehat_security.cadence import register
from whitehat_security.e4 import E4Observation, e4_calibration, evaluate as evaluate_e4
from whitehat_security.r4 import R4Case, evaluate as evaluate_r4, r4_calibration
def test_e4_known_bad_is_a_finding_and_reachability_is_not():
assert evaluate_e4(E4Observation("auth", authenticated=True)).outcome == "finding"
assert evaluate_e4(E4Observation("reach", reachable_only=True)).outcome == "pass"
assert evaluate_e4(
E4Observation("refresh", refreshed_outside_authority=True)
).outcome == "aborted"
def test_e4_calibration_fails_known_bad_before_trusting_a_pass():
report = e4_calibration()
assert report["known_bad_failed_first"] is True
assert report["outcome"] == "pass"
assert report["evidence_class"] == "fixture"
assert {item["outcome"] for item in report["known_bad"]} == {"finding", "aborted"}
assert {item["outcome"] for item in report["known_good"]} == {"pass"}
def test_r4_incomplete_inventory_is_not_a_pass():
result = evaluate_r4(R4Case("partial", "R4-A", horizon_elapsed=True, recovered=False))
assert result.outcome == "inconclusive"
destroyed = evaluate_r4(R4Case("key-only", "R4-B", key_destroyed=True))
assert destroyed.outcome == "inconclusive"
def test_r4_calibration_fails_known_bad_before_trusting_a_pass():
report = r4_calibration()
assert report["known_bad_failed_first"] is True
assert report["outcome"] == "pass"
assert {item["outcome"] for item in report["known_bad"]} == {"finding"}
assert {item["outcome"] for item in report["known_good"]} == {"pass"}
assert {item["outcome"] for item in report["inconclusive"]} == {"inconclusive"}
def test_cadence_register_schedules_nothing():
record = register()
assert record["scheduled_live_runs"] is False
families = {item["family"]: item for item in record["families"]}
assert set(families) == {"E2", "E3", "E4", "P1/P2", "R4", "ASM T-01..T-10"}
assert all(item["scheduled"] is False for item in families.values())
assert families["E3"]["declared_interval"] == "24h"
assert families["E4"]["declared_interval"] is None
assert families["R4"]["live_owner"] is None

View file

@ -263,6 +263,40 @@ def test_fixture_e3_projects_one_runtime_identity(tmp_path):
assert cleanup(lease, broker)["credential_revocation"] == "revoked"
def test_fixture_e4_and_r4_project_no_credential_and_refuse_a_live_environment(tmp_path):
cases = (
("fixture-e4", "fixture-e4", "e4-confinement", "other-substrate"),
("fixture-r4", "fixture-r4", "r4-erasure", "horizon"),
)
for target_id, approval_class, technique, route in cases:
broker = LocalBroker()
engagement = load_engagement(tmp_path, fixture_record(
engagement_id=f"WH-ENG-FIXTURE-{target_id}",
target_id=target_id, approval_class=approval_class,
techniques=[technique], routes=[route],
))
lease = admit(
engagement=engagement,
registration=load_registration(f"targets/{target_id}.json"),
broker=broker, kill_switch=KillSwitch(tmp_path / "KILL"),
now=NOW, retired=set(),
)
assert lease.identities == ()
live = load_engagement(tmp_path, fixture_record(
engagement_id=f"WH-ENG-LIVE-{target_id}",
target_id=target_id, approval_class=approval_class,
techniques=[technique], routes=[route], environment="build",
plane_namespace="whitehat", runner_image_digest="sha256:abc",
))
with pytest.raises(AuthorizationError, match="environment=fixture"):
admit(
engagement=live,
registration=load_registration(f"targets/{target_id}.json"),
broker=LocalBroker(), kill_switch=KillSwitch(tmp_path / "KILL"),
now=NOW, retired=set(),
)
def test_fixture_capacity_projects_no_identities(tmp_path):
broker = LocalBroker()
engagement = load_engagement(tmp_path, fixture_record(

View file

@ -0,0 +1,80 @@
---
id: WHITEHAT-WP-0011
type: workplan
title: "State actual scope and calibrate the missing E4 and R4 probes"
domain: infotech
repo: whitehat-security
status: finished
owner: whitehat-security
topic_slug: whitehat-security
created: "2026-10-04"
updated: "2026-10-04"
related:
- WHITEHAT-WP-0006
- WHITEHAT-WP-0008
---
# State actual scope and calibrate the missing E4 and R4 probes
`INTENT.md` owns E2, E3, E4, P1/P2, and R4 evidence, plus the cadence that
sets the exposure window. On 2026-10-04 the repository could calibrate E2,
E3, P1/P2, and ASM offline, and it had prose for E4 and R4 with no probe.
`SCOPE.md` described those prose items as capabilities. This plan records
the comparison, makes the scope match the harness, and adds the two missing
offline calibrations. It does not reopen `WHITEHAT-WP-0006` or
`WHITEHAT-WP-0008`. It authorizes no packet, database connection, or load.
The live gaps remain the ones those blocked plans already name. They are
not implementable here until the owner returns change.
## T01 — Record scope against intent
```task
id: WHITEHAT-WP-0011-T01
status: done
priority: high
```
`history/2026-10-04T0649Z-scope-against-intent.md` compares each owned intent
item with the runnable surface. `SCOPE.md` now lists what the commands do
and what they refuse. The README no longer says the facility attacks systems
on a schedule.
## T02 — Calibrate E4 offline
```task
id: WHITEHAT-WP-0011-T02
status: done
priority: high
```
`whitehat e4-fixtures` fails known-bad confinement cases before it trusts a
known-good pass. Reachability alone stays a pass. A credential refresh
outside recorded authority aborts. `targets/fixture-e4.json` is applicable
only as `fixture-e4` and projects no credential. The plane rejects that
class in a live environment. No live E4 class exists.
## T03 — Calibrate R4 offline
```task
id: WHITEHAT-WP-0011-T03
status: done
priority: high
```
`whitehat r4-fixtures` fails recovered content, readable ciphertext, a
surviving usable key, and a low-entropy oracle before it trusts a clear
result. An incomplete copy inventory stays inconclusive. `targets/fixture-r4.json`
projects no key. `audit-core` is not registered as an R4 target.
## T04 — Publish the unscheduled cadence register
```task
id: WHITEHAT-WP-0011-T04
status: done
priority: medium
```
`whitehat cadence` states E2, E3, E4, P1/P2, R4, and ASM T-01…T-10. E3 keeps
its declared 24h detection window. Every family has `scheduled: false`, and
`scheduled_live_runs` is false. The register does not start a run.