# D2 admission preflight — read-only, against frozen driver `7580a6b2`

Author: hertz. 2026-09-13, 03:46–03:50Z. Authorized by doyle as a **read-only** preflight:
status-filtered CI queue, ancestry-authenticated census, resolved hashes, executable identity,
absent `fp-home-d2`, trial-port/rule baseline, and the presence and suitability of the proposed
`Tailscale-In` control.

**No rule was written. No listener was launched. No elevation was requested or obtained. No
setup was dispatched.** Every firewall touch below is a read.

**All capture output here is labelled `ADMISSION-PREFLIGHT` and is NOT a t1/t2/t3 of a field
run.** It shares the `t1` *contract name* only because the instrument requires one of three;
the run id says what it is, and nothing here may be read as a field observation.

## Verdict up front

**NOT CLEAR TO LAUNCH.** Revised 2026-09-13 after doyle's correction and a second, corrected
read. The standing blockers are now:

1. **RESOLVED — never a host gap, and the claim was mine to withdraw.** I reported
   `-TracePolicyStoreSource` as unsupported by this host. **doyle read the documented syntax and
   the switch is `-TracePolicyStore`**; the other spelling does not exist and was carried through
   this contract and its reviews by more than one of us. Corrected in the capture and exercised
   against the real provider: `errors=0`, both trace records `query_outcome=OK`. Section 4 is
   rewritten rather than deleted.
2. **The proposed control `Tailscale-In` is UNSUITABLE** — a DisplayName, not a `Name`, and
   ambiguous across two rules. **A sound candidate has now been measured**: the guard rule's own
   `Name`, exactly one match, every observation present (section 3a).
3. **NEW, and it blocks a green verdict — an instrument defect in `d2_validate.py`.** Its
   soundness rule *requires the negative control to raise an error*, so a clean empty
   negative-control result scores as unsound. That contradicts doyle's ruling and would report
   `sound=false` on every field run whatever the control does (section 3b). **Not repaired —
   reported.**

## 1. Green items

| check | result |
| --- | --- |
| executable identity | `72d309011415b419aabfdb0b4065df1183bfd0069a9f95b5779da62e43349b10` — **matches** the driver's pinned `EXE_SHA` exactly |
| staged helper pins (`INSTRUMENTS-STAGED-r5.sha256`) | 6/6 **OK** |
| `fp-home-d2` in the real staging `SP` | **ABSENT** — S0b's pre-existence refusal passes |
| CI queue, the driver's own five filters | `queued 0`, `in_progress 0`, `requested 0`, `waiting 0`, `pending 0`. Newest run `completed/success`, 2026-09-12T05:46:11Z |
| ancestry-authenticated census | `valid=YES procs=595 self_seen=True cargo_build=0 runner_descended=0 RUNNER_WORKER=ABSENT runner_procs=0 analyzer=6 shim_unresolved=0` |
| trial port 29470 | `MATCHING_RULES 0`, `LISTENERS 0`, `LISTENER_PROVIDER OK` — free of both rules and listeners |
| guard port 5470 baseline | `MATCHING_RULES 1`, `LISTENERS 0`; one Inbound/Allow/Private TCP 5470 rule from `192.168.1.0/24`, `Program=Any`, recorded for the guard diff |

The census's six candidates are all `class=analyzer`, each resolving through a
`claude → claude-spt → spt` chain, so they fall under the standing analyzer exemption; `box_gate`
would pass. The port instrument reports its own positive controls non-zero
(`CTL_RULES_NONZERO 1`, `CTL_SPTEXE_RULES 7`) and `1001` rules enumerate in the store, so none of
the zeros above is a dead-enumeration zero.

## 2. Instrument staging — gap found, then closed on authorization

S0c requires **six** instruments at `$BIN`. Only **four** were staged: `d2_capture.ps1` and
`d2_validate.py` were **ABSENT**, and the driver would have died at S0c before touching anything.
Found by preflight, reported, and staged only after doyle authorized it.

Staged by copy, source hashes read **before** and destination hashes re-read **after**:

```
bccd9be883d835e6346ff77bcd0e5de75a35dd8fdd362a17795a393f603c38c6  fp-bin/d2_capture.ps1
b922d4b4084466f1298258c33bcff7c9c30b5bd6dc44af66f971ddede9ef8f85  fp-bin/d2_validate.py
```

**Re-staged later the same session**, after doyle authorized the trace-switch repair (section 4):
`fp-bin/d2_capture.ps1` is now `1be4dfe2c39eccc4782c089941b7425845554978cc053dc05ad71410ba25ec00`,
again hashed at the source before the copy and re-read at the destination after it. The D2
manifest carries that row and the reason for it, and `sha256sum -c` reads 6/6 OK.
`d2_validate.py` is unchanged at `b922d4b4…` — the defect in section 3b was **reported, not
repaired**.

New manifest `INSTRUMENTS-STAGED-D2.sha256` in the staging `SP`, covering the **actual resolved
six**; `sha256sum -c` reads 6/6 OK. `INSTRUMENTS-STAGED-r5.sha256` is **preserved unchanged** and
still verifies 6/6, and `cpubracket.ps1` is left staged and unused — nothing was deleted. The
driver row is kept beside the manifest, not inside it (different tree):
`7580a6b2…` for `fp-driver-d2.sh`.

## 3. BLOCKER — the proposed control, measured

Probed two ways, read-only:

```
BY_NAME_COUNT=0                     Get-NetFirewallRule -PolicyStore ActiveStore -Name 'Tailscale-In'
BY_DISPLAYNAME_COUNT=2              ... -DisplayName 'Tailscale-In'
ANY_TAILSCALE_LIKE_COUNT=3
  Name={08827CF3-…}  DisplayName=Tailscale-Process  Inbound Enabled Allow
  Name={FEDFE810-…}  DisplayName=Tailscale-In       Inbound Enabled Allow
  Name={DC0001DF-…}  DisplayName=Tailscale-In       Inbound Enabled Allow
TOTAL_ACTIVESTORE_RULES=1001
```

**Two independent reasons it cannot serve as the positive control:**

1. **Not addressable by the route the capture uses.** `d2_capture.ps1` resolves every rule with
   `-Name`. `Tailscale-In` is a DisplayName; the rules' `Name`s are GUIDs. By `-Name` the store
   answers with nothing.
2. **Ambiguous even by DisplayName.** *Two* rules answer to it. A positive control must identify
   one object; two would land as `matched_count=2`/`ambiguous=true`, which is the very condition
   the instrument exists to flag.

Run through the real capture instrument, the store's answer was a **successful empty result**,
not a refusal — `role=positive-control found=False matched_count=0 query_outcome=OK`, and
`elevated=False` recorded honestly. This matters: an empty control here is not an access
denial, so it must not be read as one.

The driver's own validator states the consequence:

```
D2CONTROLS positive=NO_RULE negative_returned_nothing=true negative_raised_error=false sound=false
```

The negative control behaved exactly as designed (returned nothing, raised no error). But with
`positive=NO_RULE` the control set is **unsound**, and an unsound control set is not a cosmetic
problem: the positive control is the only thing that separates *"this property is absent on the
pair"* from *"this instrument cannot read this property"*. Without it every absence in the pair's
record is **unreadable**, which is most of what the capture exists to establish.

**I have not selected a replacement**, per doyle's instruction to report first. Stated for the
decision, not as a recommendation: any replacement must be addressable by `-Name`, must match
**exactly one** rule, and should carry the property set the pair carries so that a shared absence
reads as an instrument limit.

## 3a. The candidate positive control, measured through the corrected capture

Queried by the guard rule's own `Name`, as doyle directed:

```
Get-NetFirewallRule -PolicyStore ActiveStore -Name '{FF36EE51-2837-46D8-9AB8-2441D9070633}'
  found=True   matched_count=1   ambiguous=False   query_outcome=OK
```

**Exactly one match.** All seven filter groups returned one match each with `query_outcome=OK` and
every property `PRESENT`: security (`LocalUser`, `RemoteUser`, `RemoteMachine`), address
(`LocalAddress`, `RemoteAddress`), port (`Protocol=TCP`, `LocalPort`, `RemotePort`, `IcmpType`),
application (`Program`), iface (`InterfaceAlias`), ifacetype (`InterfaceType`), service
(`Service`).

Raw rule observations, all `PRESENT` except one:

| property | state | value |
| --- | --- | --- |
| `Name` / `InstanceID` | PRESENT | `{FF36EE51-2837-46D8-9AB8-2441D9070633}` |
| `DisplayName` | PRESENT | `spt lan-bootstrap 5470` |
| `Enabled` | PRESENT | `{rendered: True, underlying: 1}` |
| `Direction` | PRESENT | `{rendered: Inbound, underlying: 1}` |
| `Action` | PRESENT | `{rendered: Allow, underlying: 2}` |
| `EdgeTraversalPolicy` | PRESENT | `{rendered: Block, underlying: 0}` |
| `Profiles` / `Profile` | PRESENT | `2` / `{rendered: Private, underlying: 2}` |
| `EnforcementStatus` | PRESENT | `"Enforced"` — a **scalar string**, cardinality 1 |
| `PrimaryStatus` | PRESENT | `{rendered: OK, underlying: 1}` |
| `Status` / `StatusCode` | PRESENT | parsed-successfully text / `65536` |
| `PolicyStoreSourceType` / `PolicyStoreSource` | PRESENT | `{rendered: Local, underlying: 1}` / `PersistentStore` |
| `Group`, `Owner`, `Description` | PRESENT | `null` — present-and-null, not absent |
| `PolicyStore` | **ABSENT** | the provider did not expose it |

Three things worth naming, none of them a verdict:

* The rendered/underlying split this whole investigation is about **works against a real
  provider**, not only against stubs: six properties came back carrying both halves.
* `PolicyStore` **ABSENT on the control** is exactly what a positive control is for. An absence
  shared with the pair would then be an instrument limit, not a fact about the pair.
* `EnforcementStatus` here is a **scalar string** `"Enforced"`, where the contract's concern is a
  `uint16[]` such as `[5,20]`. That is a datum about **this** rule; it neither confirms nor
  refutes the array case, and it is recorded rather than generalised.

This rule is **our own** production 5470 rule — the guard the run must not disturb. Read-only use
is consistent with that, it must not be written, and nothing here wrote it. It stays a
**candidate** until doyle accepts it.

Recorded from todlando, not folded into anything: the cmdlet's `-PrimaryStatus` accepted-values
list says `Unknown, OK, Inactive, Error` while its own prose and the class enumeration say
`Degraded` for value 2. The capture keeps the number, so if that value ever comes back, whichever
label is attached is a deliberate choice rather than one taken from whichever page was open. It
did not arise here — this control returned `OK`/`1`.

## 3b. NEW BLOCKER — `d2_validate.py` scores a clean negative control as unsound

With the positive control present and every observation made, the validator still says:

```
D2CONTROLS positive=PRESENT negative_returned_nothing=true negative_raised_error=false sound=false
```

The rule, at `d2_validate.py:247`:

```python
controls_sound = (pos_state == 'PRESENT') and neg_nothing and neg_error
```

It **requires the negative control to have raised an error**. doyle's ruling is the opposite and
is right: a successful empty negative-control query is not an error. As written, `sound=false`
cannot be fixed by choosing a better control — it would read `false` on every field run, a latent
false negative on the one line that says whether the pair's absences can be read at all.

It survived until now because the negative control's error was **synthesised by the stubs**. Only
a real provider, asked for a name that genuinely does not exist, returns nothing quietly.

**Not repaired.** Changing a soundness rule is not the narrow switch repair I was authorized to
make, and this is the third instrument-versus-reality mismatch surfaced in one session — the
pattern says report and let doyle rule.

## 4. WITHDRAWN — the trace switch was ours, not the host's

**My earlier claim in this receipt was wrong and is replaced, not annotated away.** I wrote that
`-TracePolicyStoreSource` was unsupported by this host's NetSecurity module, called it a host
capability gap, and called it the first thing this investigation had learned from a real provider.
A binding error on a parameter that does not exist is not a fact about the host: it is a query
that could never have run.

doyle read Microsoft's `Get-NetFirewallRule` syntax independently and named the real switch,
`-TracePolicyStore`. todlando reached the same reading and named the trap that produced it: the
`MSFT_NetFirewallRule` **class** reference describes the option in prose as "the
TracePolicyStoreSource option", and that prose is not the **cmdlet's** parameter spelling — the
two differ by a whole word.

**Verified against the installed command before editing anything:**

```
MODULE=NetSecurity VERSION=2.0.0.0
TRACE_PARAMS_FOUND=1   PARAM=TracePolicyStore TYPE=SwitchParameter
HAS_TracePolicyStore=True        HAS_TracePolicyStoreSource=False
```

**Repair and its exercise.** `d2_capture.ps1` now sends `-TracePolicyStore` and describes it that
way in the query text; parse check 0 errors; `bccd9be8` becomes `1be4dfe2`. Exercised on the
corrected read-only route against the real provider:

```
errors[] = []                                          (was 2)
... -Name 'spt-core-bootstrap-inbound-tcp'     -TracePolicyStore  -> query_outcome=OK
... -Name 'spt-core-bootstrap-inbound-tcp-lan' -TracePolicyStore  -> query_outcome=OK
```

`found=False matched=0` on both, which is correct: the pair does not exist before setup.

The document key `trace_policy_store_source_used` **keeps its name for this diagnostic** (doyle);
`d2_validate.py` pairs records on that exact key, and it now records use of `-TracePolicyStore`.
Marked at the site so it is not silently misread.

**The failed first preflight is preserved, errors and all** — `preflight-capture.json`,
`capture-probe.out`, `validate.txt`. Nothing was suppressed and no unrelated suite was re-run.

**Exercise scope does not transfer by assertion.** The leaf serializer control (12/12, both
editions) and the document control (41/41) in `d2-control-receipt.md` describe **`bccd9be8`** and
stay bound to it. What covers the changed query path at `1be4dfe2` is the focused switch change
plus this corrected real-provider read; unchanged serializer logic is not re-proven wholesale
(doyle).

## 5. What the validator said about the probe document, read honestly

```
D2VALIDATE point=t1 required=43 records_present=43 missing=46 denied=0 error=2 no_rule=0
D2VALIDATE verdict=INCOMPLETE point=t1
```

* `records_present=43/43` — the document carries every record the contract requires.
* `denied=0` — **unelevated reads on this route were not denied.** Whatever elevation is needed
  for the setup, it is not needed to read these properties, at least not for a store that
  contains no matching rule.
* `missing=46` — **an artifact of the probe, not a defect.** I ran the `t1` contract against a
  host with no pair and no positive control, so there was nothing for those observations to
  observe. It is reported here so the number is not later mistaken for a driver finding.
* `error=2` — §4.
* `verdict=INCOMPLETE`, exit 1 — correct for a pre-setup probe under a post-setup contract.

## 6. The admission sheet — exact sequence, with the shell named

Binding for this admission (doyle): **the setup command is a Bash/MSYS command line and must be
run by Bash, not PowerShell.** The `-Command "…; exit $LASTEXITCODE"` form applies to the t1
capture and to the six stamps, which are PowerShell, and to nothing else. The generated request's
sentence "Run each command with powershell.exe …" over-generalises; it is a **document-text defect
in `7580a6b2`, recorded as owed** and to be corrected in the next successor — `7580a6b2` is frozen
and will not be unfrozen for wording.

The elevated leg, in order, one leg, one nonce:

| step | shell | what runs |
| --- | --- | --- |
| 1 | PowerShell | `stamp_utc_now_to … setup-start.utc` |
| 2 | **Bash / MSYS** | `env -u SPT_INSTALL_NO_FIREWALL SPT_HOME="<H>" "<EXE>" serve lan --bootstrap --port 29470 > <…-command.out> 2> <…-command.err>` |
| 3 | Bash | preserve `$?` in a variable **immediately** |
| 4 | PowerShell | `stamp_utc_now_to … setup-return.utc` |
| 5 | either | write the step-3 exit to `setup.exit` — **before t1 runs** |
| 6 | PowerShell | `stamp_utc_now_to … d2-t1-start.utc` |
| 7 | **PowerShell**, the exercised form | `powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "& '<BIN>/d2_capture.ps1' -CapturePoint 't1' -RuleName @('spt-core-bootstrap-inbound-tcp','spt-core-bootstrap-inbound-tcp-lan') … -SetupIntervalStart (Get-Content -Raw '<…setup-start.utc>').Trim() -SetupIntervalEnd (Get-Content -Raw '<…setup-return.utc>').Trim(); exit \$LASTEXITCODE"` then its own exit to `d2-t1.exit` |
| 8 | PowerShell | `stamp_utc_now_to … d2-t1-end.utc` |

The driver writes the concrete paths into the request at run time; the request is the authority,
this table names the **shell per step**.

Driver launch, unelevated, from its pinned location (no second copy, so one file = one hash):

```
bash /c/Users/decid/Documents/projects/spt-core/.spt/preserved/hertz-fp-driver-review/d2/fp-driver-d2.sh
```

with `sha256sum` on that path reading `7580a6b2…` immediately before launch.

## 7. Acceptance conditions carried into the field run

Recorded as **evidence gaps** — the complete diagnostic contract is **not** satisfied while either
is unresolved (doyle):

* **Timestamp order.** `TIMESTAMP_ORDER` must read `OK`. `UNMEASURED` (a missing or unparseable
  stamp) and `VIOLATED` are both gaps, and neither may be read as a pass.
* **Exit cross-check.** `SETUP_EXIT_PRESERVED` must agree with the receipt's `exit=`. `ABSENT`,
  `UNPARSEABLE` and `DISAGREES` are each a gap on the setup exit's provenance.
* **`ActiveStore+trace`** will be missing on this box until §4 is resolved.
* **The positive control** must be sound, or every absence in the pair's record is unreadable.

No new control pass is needed merely to restate these.

## 8. Correction to my own earlier receipt

`d2-successor-receipt.md` §7 said r10's `fp-home` was "untouched" on the strength of an unchanged
directory mtime. **An unchanged directory mtime does not prove the contents were untouched**
(doyle). It is a **boundary observation**: the directory's own mtime is `2026-09-12 16:46:25
-0700`, unchanged across this work, and nothing at its top level is newer than 16:46. That is
consistent with the contents being untouched and is not proof of it. The successor also never
names that path — `H` is `fp-home-d2` — so the design reason to expect it untouched stands on the
path, not on the mtime.

## 9. Artifacts

All preflight output is under this session's scratchpad `preflight/` directory.

**First probe, the one that failed — preserved, not suppressed:** `preflight-capture.json`
(sha256 `259b857a80e7b982d5e07a242df4f6da68c6c53780185a92eda07f3ff2a79fed`, 273667 bytes, carrying
both `-TracePolicyStoreSource` binding errors), `capture-probe.out`, `validate.txt`.

**Second probe, on the corrected route:** `preflight2-capture.json` (sha256
`b79455bfbb00bd3873bfa35e830c2a43a991355e2600bbdebf98c2df67edbfa8`, 276763 bytes, `errors=0`),
`capture-probe2.out`, `validate2.txt`.

**Shared by both:** `runner-census.txt`, `portfields-29470-*.txt`, `portfields-5470-*.txt`.
