ADVERSARIAL REVIEW -- D-139 STEP 6 TOOL
=======================================
Date : 2026-08-02
Reviewer : adversarial review pass (subagent), jumphost vcloud
Targets : netbox/d139-step6-vip-rehome.py (untracked, written 2026-08-02)
tests/d139-step6-vip-rehome/run-tests.sh (untracked, written 2026-08-02)
Governing : docs/design-decisions.md
- "ORDERING RULING 2026-08-02 -- D-139 step 6 executes IN FULL before the
Stage-5 deploy" -- operator: "Full step 6 first, then deploy"
- "AMENDMENT 2026-08-02 -- D-139 step 6: 'retire' means DEPRECATE, and
nothing is deleted" -- operator: "Deprecate both, delete nothing"
- "CORRECTION NOTE 2026-08-01 -- D-139: the EXECUTION list was defective"
Apex : http://10.10.1.10:8000 (office1-netbox, the WORKING VR1 apex, DOCFIX-195)
VERDICT: FIX FIRST
Do NOT run --commit as the tool stands. Two MEASURED defects are reachable without any
operator error beyond choosing the other valid value of a required flag, and the harness
does not test the one property both operator rulings name as load-bearing.
The tool's core mapping logic is CORRECT and its dry run reproduces the ruling's
measured 26 / 26 / 9 exactly. The defects are missing PRECONDITIONS and missing
COVERAGE, not a wrong carve.
METHOD AND HONESTY BOUNDARY
MEASURED = observed from a command run this session, output reproduced below.
REASONED = derived from reading the source; explicitly labelled where used.
Nothing in the DEFECTS section is REASONED-only; every one carries a run.
--commit was NEVER executed. All live apex access was read-only (GET).
Mutation experiments were run against COPIES under
/home/jessea123/.claude/jobs/015c103e/tmp/mut/ . No repo file was edited at any point.
-------------------------------------------------------------------------------
BASELINE -- what the tool does today, MEASURED
-------------------------------------------------------------------------------
$ . ~/vr1-office1-creds/vr1-netbox-sandbox.env
$ python3 netbox/d139-step6-vip-rehome.py --dc vr1-dc0 (DRY RUN, rc=0)
D-139 STEP 6 -- vr1-dc0 -- apex http://10.10.1.10:8000
CREATE 26 | ALREADY 0 | DEPRECATE-ADDR 26 | DEPRECATE-PFX 9
The 26 creates are 2602:f3e2:f02:20::50-::62 (metal-admin) and
2602:f3e2:f02:21::50-::62 (metal-internal), status=reserved, descriptions verbatim.
This matches the ORDERING RULING's measured state exactly. Host octets are preserved,
the ULA->GUA hextet mapping is right, and the /60 parents are deprecated but hold no
addresses. The offline harness reports PASS=14 FAIL=0.
Independent read-only enumeration of the apex (152 prefixes, 160 addresses, 27 ranges)
confirms, MEASURED:
- 52 ULA v6 addresses exist: 26 in dc0 (fd50:...:220,:221), 26 in dc1 (:320,:321).
- All 26 dc0 ULA VIP records carry status=reserved. VIP_STATUS="reserved" is correct.
- Zero ip-ranges inside the retired ULA /48. Zero duplicate prefix CIDRs apex-wide.
- No provider-public ULA /64 exists, so the ambiguity REFUSE (:10 vs :11) is not
reachable on this apex today. It is a live-safe design, not a live-blocking one.
CHECKED AND CLEAN (a gap I suspected and disproved):
overlays/vr1-dc0-vips.yaml declares 39 GUA VIP legs -- f02:11 x13, f02:20 x13,
f02:21 x13 -- and ZERO fd50: legs. Step 6 only creates 26. The missing 13 are NOT a
gap: 2602:f3e2:f02:11::50-::62 ALREADY EXIST in the apex as ip-address records
("VIP <svc> provider-public v6 (vr1-dc0) -- octet mirror ::NN"). So 13 existing + 26
created = the full 39, and after step 6 the apex records every VIP the overlay
declares. No action needed.
-------------------------------------------------------------------------------
DEFECTS, RANKED BY CONSEQUENCE
-------------------------------------------------------------------------------
DEF-1 [CRITICAL -- defect MEASURED, consequence REASONED]
--dc vr1-dc1 creates 26 ORPHAN addresses and deprecates dc1's only valid records.
No precondition check that step 1 ran for the DC being operated on.
--dc accepts choices=sorted(C.DC_GUA), i.e. BOTH vr1-dc0 and vr1-dc1
(d139-step6-vip-rehome.py:152). vr1-dc1 is one keystroke from vr1-dc0.
MEASURED, the apex's vr1-dc1 GUA carve is INCOMPLETE -- only 4 rows exist under
2602:f3e2:f03::/48, all provider-public:
2602:f3e2:f03::/48 vr1-dc1 dc (container)
2602:f3e2:f03:10::/60 vr1-dc1 provider-public
2602:f3e2:f03:10::/64 vr1-dc1 provider-public
2602:f3e2:f03:11::/64 vr1-dc1 provider-public
There is NO 2602:f3e2:f03:20::/64 and NO 2602:f3e2:f03:21::/64.
MEASURED, the tool nevertheless plans (dry run, rc=0, no warning of any kind):
D-139 STEP 6 -- vr1-dc1 -- apex http://10.10.1.10:8000
CREATE 26 | ALREADY 0 | DEPRECATE-ADDR 26 | DEPRECATE-PFX 9
... 2602:f3e2:f03:20::50/64 .. :20::62, 2602:f3e2:f03:21::50 .. :21::62
DEPRECATE prefixes: fd50:840e:74e2:320::/60, :320::/64, :321::/64,
:330::/60, :330::/64, :340::/60, :340::/64,
:350::/60, :350::/64
With --commit this would write 26 ip-addresses into /64s that DO NOT EXIST as prefix
rows, then mark dc1's 26 authoritative ULA addresses and 9 ULA prefixes deprecated,
and exit 0. dc1's VIPs would then be recorded ONLY as orphans with no containing
prefix, while every row that currently describes them reads unusable.
This is exactly the orphaning hazard DEFECT 3 of the 2026-08-01 CORRECTION NOTE
names, and exactly what the ordering ruling exists to prevent -- reached not by a
race or a partial failure but by the tool's own documented interface.
Note the asymmetry that makes this easy to miss: gua_net_for_role() computes the
target /64 ARITHMETICALLY from C.DC_GUA and C.CARVE. It never asks the apex whether
that /64 exists. For vr1-dc0 all 16 target prefixes happen to exist (step 1 applied),
so dc0's dry run looks perfect and hides the missing check entirely.
Compare the imported tool: d139-gua-carve.py checks its preconditions before writing
(exactly one site with the slug, line 177-180; every CARVE role exists in the apex,
line 183-186, "refusing to half-carve a DC"). d139-step6-vip-rehome.py checks none.
FIX (suggested, operator's call): before planning, require that every GUA /64 the plan
targets EXISTS as a prefix row scoped to --dc, and die() if not, naming step 1.
Optionally also restrict --dc to vr1-dc0 for this window.
DEF-2 [CRITICAL -- defect MEASURED, consequence REASONED]
The SANDBOX_HOSTS apex-identity guard present in the imported tool is ABSENT from
the new tool. It will write to whatever NETBOX_URL names.
d139-gua-carve.py carries the guard:
:25 SANDBOX_HOSTS = {"localhost", "127.0.0.1", "10.10.1.10"}
:161 if host not in SANDBOX_HOSTS:
:162 die("'%s' is not the VR1 working apex. office1-netbox (10.10.1.10) takes ALL
VR1 reads and writes; netbox.baldurkeep.com is the v1 REFERENCE and stays
untouched (DOCFIX-195)." % host)
d139-step6-vip-rehome.py: grep -c SANDBOX_HOSTS -> 0. No urlsplit, no hostname check.
The guard lives inside d139-gua-carve.py's main(), which the import never executes, so
subclassing C.NB inherits the transport but NOT the safety posture.
MEASURED differential against a non-sandbox host (127.0.0.9:1, unreachable, dummy
token -- nothing real was contacted):
carve: REFUSE: '127.0.0.9' is not the VR1 working apex. ... (DOCFIX-195) rc=2
step6: REFUSE: apex unreachable or rejecting: GET /ipam/prefixes/?limit=500
-> <urlopen error [Errno 111] Connection refused> rc=2
Both exit 2 here only because the host is unreachable. The carve tool refused on
IDENTITY, before any network call; the step-6 tool refused on REACHABILITY. Against a
REACHABLE non-sandbox apex -- netbox.baldurkeep.com, the v1 REFERENCE that DOCFIX-195
says stays untouched -- the step-6 tool would proceed to plan and, with --commit,
write. One wrong `.` of an env file is the whole distance.
This defect is a REGRESSION against the imported module's own stated posture, which is
the strongest argument for fixing it: the reviewer of step 1 already decided this
guard was required for a WRITE tool against this apex.
FIX: lift the same guard (reuse C.SANDBOX_HOSTS, do not retype the set).
DEF-3 [HIGH, MEASURED] SILENT UNDER-COUNT: a missing or mis-scoped ULA /64 prefix row
silently halves the work and the run still exits 0.
dep_addrs and creates are derived ONLY from addresses contained in a /64 prefix ROW
that is scoped to --dc and inside the retired ULA /48 (plan(), lines 102-141). The
addresses themselves are never counted independently, and no expected total is
asserted anywhere.
MEASURED offline against the pristine repo file, full 26-address fixture, with only
the fd50:840e:74e2:221::/64 PREFIX row removed (its 13 addresses still present):
D1 the :221::/64 prefix ROW is absent
-> CREATE=13 DEPA=13 DEPP=8 ALREADY=0 -- no complaint, plan() returns
D1b the :221::/64 row's scope is blank/name-only instead of the slug
-> CREATE=13 DEPA=13 DEPP=8 ALREADY=0 -- same
Thirteen live VIPs would be neither created nor deprecated, and the run would report
success. D1b is not hypothetical shape-wise: scope_slug() itself exists because the
live API returns scope.slug while repo dumps normalise scope.name, and its own
docstring records that matching on name alone "found ZERO live".
This is the same class as CORRECTION NOTE DEFECT 2 -- dc-node-v6-carve.py carving FOUR
FEWER PLANES PER NODE and EXITING CLEAN -- and the same class as the MEMORY entry
"instrument currency before negatives". The carve tool guards its half of this
(build_retire(): "the apex returned ZERO prefixes scoped to %s ... refusing to call
every plane 'missing' on that evidence"). The step-6 tool guards only the total-zero
case (`if not ula_pfx`), not the partial case, which is the one that actually bites.
FIX: assert a floor. The ruling records 26 / 26 / 9 as MEASURED; make the tool require
it (e.g. --expect-creates / --expect-deprecate-addrs / --expect-deprecate-prefixes,
or cross-check dep_addrs against the count of ALL v6 addresses inside the retired /48
that are NOT in another DC's scoped prefixes). Any tool that can do less than the
ruling specifies and still exit 0 is not gated.
DEF-4 [HIGH, MEASURED] The HARNESS does not test main() at all -- so the CREATE ->
verify -> DEPRECATE ORDER, the one property both rulings name, is UNCOVERED.
All of T1-T12 call plan(). Nothing calls or inspects main(). The harness header states
"the tool's write path is CREATE -> verify -> DEPRECATE ... plan() is what decides all
three sets, so it is where the risk lives" -- that sentence is the bug. plan() decides
the SETS; main() decides the ORDER, and ORDER is what the ruling constrains:
"CREATE FIRST, VERIFY, THEN DEPRECATE -- reversed, there would be an interval in which
the apex marks a live VIP's only record unusable."
MUTATION M1b, MEASURED. Into a COPY of the tool, immediately before the "PHASE 1:
CREATE" block, inserted:
for _x in dep_addrs:
nb.patch("/ipam/ip-addresses/%d/" % _x["id"], {"status": DEPRECATED})
for _x in dep_prefixes:
nb.patch("/ipam/prefixes/%d/" % _x["id"], {"status": DEPRECATED})
i.e. the tool now deprecates every ULA row BEFORE creating a single GUA record --
precisely the forbidden ordering. Harness result:
RESULT: PASS=14 FAIL=0
ALL PASS
The suite cannot see the defect it was written to prevent.
FIX: test main() with a fake NB that RECORDS the call sequence, and assert that no
PATCH is issued before every POST has been issued and read back. Also assert the
failure branches: a POST that raises leaves zero PATCHes; a read-back that comes back
short leaves zero PATCHes.
ORDERING IN THE SOURCE IS, HOWEVER, CORRECT AS WRITTEN (read, not executed): phase 1
posts all creates; a RuntimeError returns 1 with "NOTHING WAS DEPRECATED"; phase 2
re-reads /ipam/ip-addresses/ and returns 1 on any missing create, again before any
patch; phase 3 patches; phase 4 re-reads and verifies status. I found no path in the
source where a ULA row is deprecated while its GUA counterpart does not exist, EXCEPT
the vacuous case described in DEF-6 below. This is REASONED from source plus the
MEASURED dry run; the --commit path was not executed, per the review constraint.
DEF-5 [MEDIUM, MEASURED] T13, the case that names itself PROOF-OF-TEETH, is an assertion
on ABSENCE and therefore passes vacuously on ANY error in its own snippet.
T13 is `if grep -q "MUTANT_FIRST=<correct>" ; then bad ; else ok ; fi`. Any output that
lacks that string satisfies it -- including a traceback.
MUTATION M0, MEASURED. The tool file was replaced (in the COPY) with one line of
non-Python. Result:
FAIL T1 .. FAIL T12 (all report "Traceback (most recent call last):")
PASS T13 PROOF-OF-TEETH: a wrong /48 changes the target, so T2 is a real assertion
PASS T14 tool contains NO delete path
RESULT: PASS=2 FAIL=12
A tool that does not even parse passes the teeth test. Standing rule, script-authoring
reference: "prove each new assertion can FAIL *and* prove it RAN."
FIX: make T13 assert POSITIVELY -- require a MUTANT_FIRST line to be present AND to
differ from the correct target (two greps, not one negation).
DEF-6 [MEDIUM, MEASURED offline; NOT live today; reachable AFTER a first --commit run]
A pre-existing but DEPRECATED GUA record is accepted as ALREADY, and its ULA
counterpart is then deprecated too -- leaving the VIP with no usable apex row.
plan() builds `existing` as a set of bare IPs (line 113) and the phase-2 read-back
likewise compares bare IPs only (lines 205-210). Neither consults status.
MEASURED offline, pristine tool, full fixture plus one deprecated GUA record:
D3 GUA counterpart exists but is DEPRECATED
-> CREATE=25 DEPA=26 DEPP=9 ALREADY=1
::50 in creates? False ULA ::50 in dep_addrs? True
After commit BOTH the ULA and the GUA record for that VIP read deprecated.
Not live now (there are zero deprecated addresses in the apex). It becomes reachable
the moment step 6 has run once and anyone flips a GUA row to deprecated -- i.e. it is
an IDEMPOTENCE hazard on re-run, not purely theoretical. It is also the only route I
found by which a ULA row ends up deprecated without a usable GUA counterpart, so it
bears directly on the brief's item 1.
FIX: treat a non-reserved/non-active existing GUA record as a REFUSE, or PATCH it back
to VIP_STATUS as part of the create phase, and include status in the read-back
predicate.
DEF-7 [MEDIUM, MEASURED] T14's delete-path assertion covers ONE file, not the surface
the tool actually has.
Item 6 of the brief, answered directly: AS WRITTEN TODAY there is NO delete path.
Verified by reading both files -- the only HTTP methods reachable are GET (get_all),
POST (post) and PATCH (NBW.patch). C.NB._req takes an arbitrary `method` string but no
caller passes DELETE. That is a clean finding.
The ASSERTION that keeps it that way, however, greps "$TOOL" only.
MUTATION M2, MEASURED. A delete() method was added to the COPY of the IMPORTED module
d139-gua-carve.py (whose NB class step 6 subclasses), leaving the step-6 file untouched:
def delete(self, path):
return self._req("DELETE", path)
RESULT: PASS=14 FAIL=0
T14 stays green while the class the tool inherits from has grown a delete path. The
ruling "Deprecate both, delete nothing" is repo-wide; the assertion is file-wide.
FIX: grep both netbox/d139-step6-vip-rehome.py and netbox/d139-gua-carve.py, or assert
on the method set reachable from NBW.
DEF-8 [LOW, MEASURED offline; NOT live] An address inside a ULA /60 parent but outside
its /64 is silently dropped while the /60 is deprecated anyway.
plan() skips any prefix with prefixlen != 64 (line 117-118) -- correct, and necessary:
MUTATION M6 (removing that guard) makes CREATE jump 26 -> 52 and T1/T6/T7 all fire.
THE GUARD IS DOING MORE THAN PREVENTING A DOUBLE-COUNT, and this is worth stating
because it changes how much weight it carries: fd50:840e:74e2:221::/64 (metal-internal)
is CONTAINED IN fd50:840e:74e2:220::/60 (role metal-admin). With the guard removed, the
/60 matches all 26 addresses and maps the 13 metal-INTERNAL VIPs onto
2602:f3e2:f02:20::/64, the metal-ADMIN GUA plane -- 26 + 13 + 13 = 52, exactly the
measured figure. So that one `continue` is the only thing standing between the tool and
a silent CROSS-PLANE MIS-MAP. It must never be relaxed.
But dep_prefixes deprecates ALL in-scope ULA prefixes including the /60s. MEASURED,
pristine tool, fixture plus one address at fd50:840e:74e2:225::7 (inside :220::/60,
outside :220::/64):
CREATE=26 DEPA=26 DEPP=9
stray created? False stray deprecated? False
BUT its /60 IS deprecated: True
NOT LIVE: all 52 ULA addresses in the apex sit inside a /64. Latent only.
DEF-9 [LOW, MEASURED offline; NOT live] Duplicate prefix ROWS for the same CIDR produce
duplicate CREATE payloads.
NetBox permits two prefix rows with the same CIDR. plan() iterates rows, not CIDRs, and
the `already` check is against the pre-read `existing` set, never against creates
itself. MEASURED, pristine tool, fixture plus a second row for :220::/64:
CREATE=39 DEPA=39 DEPP=10
duplicate CREATE payloads: 13 (e.g. 2602:f3e2:f02:20::50/64)
NOT LIVE: zero duplicate CIDRs measured apex-wide. Theoretical.
DEF-10 [LOW, REASONED] ip_network.subnet_of() is true for a network against ITSELF, so if
fd50:840e:74e2::/48 ever exists as a prefix ROW scoped to a DC it would be deprecated
by this tool -- the org aggregate d139-gua-carve.py:48 calls "still a valid org
aggregate". MEASURED: no such row exists today (the 18 in-/48 rows are all /60 or /64).
Latent only; stated because the /48 is explicitly still-valid per the carve tool.
DEF-11 [COSMETIC, MEASURED] tests/.../run-tests.sh defines run_case() at line 23-25 and
never calls it. Dead code in a harness whose job is to be trusted.
-------------------------------------------------------------------------------
ASSERTIONS THAT SURVIVED MUTATION
-------------------------------------------------------------------------------
Mutation battery, each applied to a COPY, harness re-run, restored between runs:
M0 tool replaced with unparseable text ..... T13, T14 PASS (see DEF-5)
M1b deprecate loops moved before creates .... ALL 14 PASS (see DEF-4)
M2 delete() added to imported carve module.. ALL 14 PASS (see DEF-7)
M3 VIP_STATUS reserved -> active ........... T4 fires
M4 ALREADY dedup removed ................... T6 fires
M5 ambiguity REFUSE -> pick first hextet ... T10 fires
M6 /60-parent skip removed ................. T1, T6, T7 fire
M7 dep_prefixes forced empty ............... T1, T7, T12 fire
M8 already-deprecated filter removed ....... T7 fires
CONCLUSION: T1, T4, T6, T7, T10, T12 have real teeth against plan(). T2/T3 are proven
by T13's construction (M6/M7 also move them indirectly). T13 and T14 are the two that
survive everything (DEF-5, DEF-7), and NO assertion at all covers main() (DEF-4).
CASE COUNT: claimed T1-T14; measured PASS=14 FAIL=0; count and labels agree. The
suite's cases all RAN. (Standing rule "assert the case count moved" -- satisfied.)
Note on coupling: T1-T5 all grep one shared $out from a single python run. A crash in
that run turns five cases red together, which is correct but means the five are not
five independent signals.
-------------------------------------------------------------------------------
WHAT I COULD NOT CHECK
-------------------------------------------------------------------------------
1. THE --commit WRITE PATH WAS NEVER EXECUTED (review constraint). Phases 1-4 are
REASONED from source only. No POST, PATCH or read-back behaviour is measured. In
particular I did not observe how the apex responds to a duplicate ip-address POST,
nor whether enforce_global_unique is set on this NetBox.
2. MAAS. The D-139 execution list step 6 reads "retire the ULA rows IN THE APEX AND IN
MAAS". This tool is apex-only and its docstring does not claim otherwise. I did not
check whether the MAAS half is covered by another tool or is still owed. FLAGGED as
an open scope question for the operator, not as a defect of this tool.
3. Whether --dc vr1-dc1 is intended to be run in this window at all. DEF-1 is stated as a
missing guard regardless of intent; if dc1 is out of scope the fix may simply be to
restrict the flag.
4. C.NB._req with method="DELETE" was never exercised; I verified by reading that no
caller passes it, not by running it.
5. Real-world contents of ~/vr1-office1-creds/vr1-netbox-sandbox.env -- sourced only,
never read or printed, per the secrets rule.
6. Concurrency. If anything else writes the apex between the tool's initial read and its
phase-3 patch, plan() is stale. Not evaluated.
7. The 2602:f3e2:f02:11:: provider-public VIP records' provenance -- I confirmed they
EXIST and match the overlay, but did not trace which tool created them.
-------------------------------------------------------------------------------
REPO INTEGRITY STATEMENT
-------------------------------------------------------------------------------
No repo file was edited by this review. All mutation experiments were performed on
copies under /home/jessea123/.claude/jobs/015c103e/tmp/mut/ , and the pre-mutation
copies were diffed against the repo originals at the end of the run:
IDENTICAL: netbox/d139-step6-vip-rehome.py
IDENTICAL: netbox/d139-gua-carve.py
IDENTICAL: tests/d139-step6-vip-rehome/run-tests.sh
The harness was re-run against the real repo files afterwards: PASS=14 FAIL=0.
bash scripts/repo-lint.sh -> 0 fail, 1 warn (the pre-existing L1 legacy-ASCII warn on
docs/design-decisions.md), 657 files scanned.
NOTE, stated because it would otherwise look like my doing: `git status --short`
showed ONLY the two untracked targets at the start of this review, and later showed
docs/CURRENT-STATE.md, docs/design-decisions.md and tests/HARNESS-MANIFEST as modified.
I inspected those diffs: they are the status=active -> status=reserved correction and
the HARNESS-MANIFEST entry for d139-step6-vip-rehome -- concurrent edits from the
parent session, not from this review. No git add / commit / push was run.
No cloud mutation of any kind was performed. All apex access was GET.
-------------------------------------------------------------------------------
SUMMARY OF REQUIRED FIXES BEFORE --commit
-------------------------------------------------------------------------------
MUST DEF-1 precondition: every target GUA /64 must exist as a prefix row for --dc
MUST DEF-2 restore the SANDBOX_HOSTS apex-identity guard (reuse C.SANDBOX_HOSTS)
MUST DEF-3 assert the expected 26 / 26 / 9 floor; partial must not exit 0
MUST DEF-4 harness must cover main()'s ordering and its two failure branches
SHOULD DEF-5 make T13 assert positively so it cannot pass on a traceback
SHOULD DEF-6 include status in the ALREADY / read-back predicate
SHOULD DEF-7 extend T14's grep to the imported module
MAY DEF-8..DEF-11 latent / cosmetic; record rather than fix if time is short
The mapping logic, the never-delete posture, the status choice, the verbatim
descriptions and the source-level phase ordering are all correct and were verified.
This is a good tool missing its preconditions and missing the test for its own
headline property.