# 2026-07-14 -- C4 (sandbox loop documented) + the upstream-write guard it exposed

## C4 -- the NetBox sandbox loop is now documented (Stage 2 close-out)

Two placements, no duplication of rationale:
- `docs/dc-dc-netbox-buildout-scope.md` **section 8** -- the design: why a sandbox, why a fidelity
  check (the 17-of-90 silent scope drop), the 2026-07-14 hardening (do not trust a pre-hardening
  "faithful" verdict), the five-tool write-guard table, the WAF trap, and the C2 preconditions.
- `runbooks/dc-dc-phase1-office1-standup.md` **Step 10b** -- the operational sequence (dump ->
  seed -> apply delta -> fidelity-check -> operator-gated upstream write), with the two-host /
  two-token separation called out as the safety property. Step 10's stale "the literals do not
  exist yet" note was corrected (D-115/117/118 assigned them).

Drafted by a subagent, then corrected against this session's own changes before placement (the
draft predated the two fixes below and would otherwise have documented a now-false state).

## The guard C4 exposed: the two pynetbox importers had NO upstream gate

Documenting the write-guard architecture surfaced that `roles-aggregates-import.py` and
`dc-dc-prefixes-import.py` had **no hostname gate at all** -- unlike `sandbox-seed.py` (structural
refusal) and `d115-office-carve.py` (`SANDBOX_HOSTS` + `--yes-write-upstream`). Until earlier today
the WAF's 403 on the default User-Agent was the ONLY thing stopping an accidental `--commit` against
the production apex -- and the WAF fix (`changelog-20260714-netbox-importers-waf-useragent.md`)
removed that accidental safety. So an explicit gate was required, and is now added:

- both importers gain `--yes-write-upstream`; a `--commit` whose `NETBOX_URL` host is not in
  `SANDBOX_HOSTS = {localhost, 127.0.0.1, 10.10.1.201}` REFUSES without it. Same pattern as
  `d115-office-carve.py`, so all three write-capable importers now behave identically.

This is defence-in-depth with the whole-plan preflights (which prevent a half-write); the guard
prevents the write from *starting* against the wrong target.

## Tests

- `tests/dc-dc-prefixes-import/` 86 -> **91**: `--commit` to a non-sandbox host is REFUSED without
  `--yes-write-upstream` (and writes nothing); WITH the flag it proceeds; a sandbox host needs no
  flag. The write-path fake tests now target the known sandbox host so the guard passes transparently.
- `tests/roles-aggregates-import/` 20 -> **24**: asserts the flag, the `SANDBOX_HOSTS` gate, the
  refusal message, and that the guard actually checks the flag.

GAUNTLET ALL GREEN (58); repo-lint 0 fail.

## Stage 2 close-out after this change

C1 DONE, C3 DONE, **C4 DONE**. C2 (feed the carve upstream) is the ONLY remaining item -- a
production write, still gated, with its preconditions now documented (section 8.6): re-verify the
sandbox under the hardened check, run the pynetbox importers from office1-netbox, pass
`--yes-write-upstream`, approve each mutation.

## Revert

    git revert <this commit>

Docs + one argparse flag and guard block per importer + their tests. No NetBox was written.
