NetBox sandbox seeded from the upstream draft (129 objects) + the two-phase tooling
...
The Office1 sandbox NetBox was EMPTY, and no seeder existed. The documented loop
(upstream draft -> sandbox -> simulate -> feed back) had no first step.
netbox/prod-draft-dump.py READ-ONLY dump of the upstream draft -> JSON artifact
netbox/sandbox-seed.py applies that JSON to a SANDBOX. Dry by default.
netbox/draft/vr1-draft.json the reviewable artifact (90 prefixes, 23 roles, ...)
TWO PHASES, deliberately, rather than one source->dest script:
- the upstream token never travels (dump runs on vcloud; seed runs on
office1-netbox, where the sandbox token already lives)
- 'the sim NEVER writes upstream' becomes STRUCTURAL: the script that writes
has no upstream credential and no upstream URL, and REFUSES to write to the
instance the draft was read from (self-configuring via the draft's _source,
so it keeps working if upstream ever moves -- a hostname denylist would not)
- the JSON is diffable: you can read what is about to be written
Applied to the sandbox: 129 created, then verified idempotent (0 created / 129
present on re-run). vr1-dc0 -> f02::/48 and vr1-dc1 -> f03::/48, matching the
apex exactly, which is what D-117 exists to guarantee.
TWO BUGS FOUND AND FIXED WHILE BUILDING IT:
1. The dry run mapped only dcim.site scopes, so REGION-scoped prefixes lost
their scope -- 17 of 90 upstream, including the ENTIRE VR1 region-scoped
draft. Caught by a spot-check printing 'site None'. Fixed + reseeded
(--update), now bound to region VR1.
2. The upstream-refusal guard sat AFTER json.load() and after the banner: it
was dead code for any malformed draft and READ as safe. Guards go first.
The harness now proves it actually refuses.
tests/sandbox-seed/: 18/18 PASS. repo-lint 0 fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ZBxzAbmqwLW7jEmMG23Xw