Optimize cutover compare/monitor per the 2026-10-12 plan.

Correctness (A): normalize port-map names with the template rules (snapshot v2,
v1 replays unchanged); protect mappings/templates that are referenced; stop
dropping edited mapping text; accept pulled-but-not-shutdown ports as migrated;
validate baseline ownership on project create; one canonical key form; fix
built-in template tolerances and upgrade old packaged values in place.

Cross-vendor identity (B): single identity pipeline for every path, per-side and
per-vendor interface rules, value maps, filters after normalization, mapping
pre-flight for compare and cutover projects, SQL eligibility fixes.

Data quality (V): no_data / suspect_parse / not_collected sheet states, command
slice coverage, directional compare modes, value-sorted duplicate pairing,
before-side raw names in results and CSV.

Robustness (H): bounded auto-run history, background evaluate/finish with
polling, manual route tables out of the acceptance gate, baseline row cache,
scoped duplicate-key failures, merged HF tasks with a per-NE session cap,
same-device cutover, explicit scope_by_port_map, read-only GETs, resolvable red
tickets.

Frontend: pre-flight report, mapping table view, template editor support for the
new rules, data-state banners. Docs: canonical field contract.

Not included: Huawei status parsers (no real samples), SQL-path route compare.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-12 11:51:55 +08:00
parent fff7d63b9b
commit 315539e0fd
59 changed files with 6483 additions and 487 deletions

View file

@ -65,7 +65,9 @@ def test_pair_claim_requires_two_slots(db):
assert len(claim.claim_queued_batches(2)) == 2
def test_busy_device_keeps_both_queued_and_no_ssh_overlap(db):
def test_busy_device_keeps_both_queued_and_no_ssh_overlap(db, monkeypatch):
# These cases are about one collection per device; the cap defaults to 2.
monkeypatch.setattr(claim, "max_sessions_per_ne", lambda: 1)
db.add(BizStateBatch(id="portrait", task_id="other", ne_id="old", status="running"))
db.commit()
claim.enqueue_collect("old")
@ -125,7 +127,9 @@ def test_inline_pair_uses_two_workers_without_waiting_for_one_side(db, monkeypat
assert set(synchronized) == {"old", "new"}
def test_faster_metrics_claim_before_manual_route_round(db):
def test_faster_metrics_claim_before_manual_route_round(db, monkeypatch):
# These cases are about one collection per device; the cap defaults to 2.
monkeypatch.setattr(claim, "max_sessions_per_ne", lambda: 1)
for tid in ("ro", "rn"):
db.add(BizStateTask(id=tid, ne_id="old" if tid == "ro" else "new",
status="paused", collect_group_id="routes", collect_manual_only=True))
@ -137,7 +141,9 @@ def test_faster_metrics_claim_before_manual_route_round(db):
assert {job["task_id"] for job in jobs} == {"old", "new"}
def test_manual_route_round_cannot_starve_behind_continuous_fast_metrics(db):
def test_manual_route_round_cannot_starve_behind_continuous_fast_metrics(db, monkeypatch):
# These cases are about one collection per device; the cap defaults to 2.
monkeypatch.setattr(claim, "max_sessions_per_ne", lambda: 1)
for tid in ("ro", "rn"):
db.add(BizStateTask(id=tid, ne_id="old" if tid == "ro" else "new",
status="paused", collect_group_id="routes", collect_manual_only=True))