# 2026-07-15 -- C2 sub-task (i): sandbox re-verified faithful; fidelity checker scope false-positive fixed

## Context

C2 (Stage 2 close-out) sub-task (i), per DOCFIX-195: re-run the HARDENED fidelity
check against `office1-netbox` (the VR1 IPAM apex, a working draft), because the
"faithful" verdict predated the 2026-07-14 hardening. Executed this session from the
jumphost, off the newly-consolidated sandbox token (see SEC-009 note below) through
an SSH tunnel to `10.10.1.10:8000`.

## What happened

1. **Read-only dump** of `office1-netbox` via `netbox/prod-draft-dump.py` (token
   sourced from `~/vr1-office1-creds/vr1-netbox-sandbox.env`, never printed). Sandbox:
   3 RIRs / 29 roles / 5 regions / 11 sites / 5 aggregates / 134 prefixes.

2. **The hardened checker flagged 2 problems** -- `10.10.0.0/16` (office) and
   `172.30.0.0/16` (edge) "have NO SCOPE" -- exit 1.

3. **Investigated rather than accepted the failure.** Both are role-level container
   /16s that `netbox/d115-office-carve.py` creates with `scope=None` **by design**
   (lines 74/84). And the upstream apex draft ALREADY carries **27 unscoped** role
   containers (`10.0.0.0/8`, `172.16.0.0/12`, `23.157.124.0/24`, ...). So unscoped
   role containers are the established convention; these two conform. A full
   cross-check of all 44 delta prefixes against their INTENDED scope (from the two
   importers) was **44/44 exact** -- every DC prefix bound to the CORRECT DC site
   (f02/nibble-2 -> vr1-dc0, f03/nibble-3 -> vr1-dc1), no wrong-target binding, no
   hidden scope drop. **The sandbox is faithful.** The checker over-flagged.

4. **The checker had a false-positive defect.** Its 2026-07-14 hardening added a
   "DELTA prefix has NO SCOPE -> fail" rule to catch silent scope drops, but it
   over-corrected: it failed ANY unscoped delta prefix, including the legitimately-
   unscoped role containers. A checker that reds on a faithful sandbox is as useless
   as one that greens on an unfaithful one, so it had to be fixed before sub-task (i)
   could close on a real exit 0.

## The fix (`netbox/sandbox-fidelity-check.py` + harness)

- New `EXPECTED_PREFIX_SCOPE`: the INTENDED scope per delta prefix -- `("site",slug)`
  / `("region",slug)` / `None` for an intentionally-unscoped org container. The DC
  half is generated by `_dc_prefix_scopes()` (the D-119 DC->site identity), so the
  CIDR set and the scope map cannot drift apart; `EXPECTED_NEW["ipam/prefixes"]` is
  now `set(EXPECTED_PREFIX_SCOPE)`.
- The delta check now asserts **scope == INTENDED** (reading the dumper's
  slug-rewritten `scope_site`/`scope_region`), not merely "has a scope." This kills
  the false-positive AND is strictly STRONGER: it still catches a dropped scope, and
  now also catches a **WRONG-TARGET** binding -- a DC0 prefix bound to `vr1-dc1`, the
  exact D-117 near-miss the old presence-only test was blind to.
- **NOT fixed by exempting the two CIDRs** (that would re-open a false-green hole on
  precisely those prefixes). Intended-None is asserted like any other intended scope.
- Harness (`tests/sandbox-fidelity-check/run-tests.sh`): fixtures moved to the dump
  format (`scope_site`), T1 now sets each planned prefix's scope from the checker's
  own `EXPECTED_PREFIX_SCOPE`, and two cases added -- **T9** (wrong-target scope ->
  fail) and **T10** (legitimately-unscoped role container -> pass). 10 -> 12 tests.

## Result

- Re-run against the real sandbox dump: **exit 0** -- "SANDBOX = upstream draft +
  EXACTLY the planned delta (D-115/D-117/D-118). Nothing lost, nothing stray."
- Harness **12/12**; gauntlet **ALL GREEN (58)**; repo-lint 0 fail (1 legacy WARN).
- **C2 sub-task (i) is CLOSED** -- the sandbox is faithful, established by a checker
  that no longer false-fails. Sub-task (ii) (load + verify the D-120 ip-ranges +
  service IPs) remains, with its logged tooling gap (dumper/checker don't yet cover
  ip-ranges/ip-addresses).

## SEC-009 note (sandbox token consolidation)

The SEC-009 consolidation had MISSED the sandbox NetBox token: it lived only at
`/root/netbox-secrets/api.token` on office1-netbox, never in `~/vr1-office1-creds/`,
contrary to the standing convention ("ALL sensitive files for a site live in
`~/<site>-creds/`"). Consolidated this session to
`~/vr1-office1-creds/vr1-netbox-sandbox.env` (0600), token copied off the VM without
being printed. Recorded in the SEC-009 row.

## Revert

- `git checkout HEAD -- netbox/sandbox-fidelity-check.py tests/sandbox-fidelity-check/run-tests.sh`
  restores the pre-fix checker (which false-fails on the two role-container /16s).
- `git checkout HEAD -- docs/security-ledger.md docs/session-ledger.md` reverts the notes.
- `git rm docs/changelog-20260715-fidelity-check-scope-correctness.md`.

No NetBox state was changed by any of this -- the dump and the check are read-only,
and the token consolidation is a jumphost-local file copy.
