Regression coverage added with the fixes:
- capture-slot semantics: second-port refusal, failed-start rollback, and
a non-owner port's stop not reaching `capture stop_kernel`;
- QEMU stop/start teardown on a mid-body or pre-launch failure, IOU start
cleanup and filter guards, Dynamips half-attach undo plus retry;
- switch VLAN reconcile skipping a deferred anchor and rolling the
ports_mapping back on failure;
- Docker/QEMU occupied-port refusal, probe ctime invalidation, probe
survival of a failing uBridge start;
- env-marker dialects (mid-line mentions, falsy values) and the agent
merge rejecting entries without a device_name.
Test-body imports of product modules move to the shared header.
The rebase onto upstream 3.1 (mypy + ruff I001/F401 waves) left the tree
short of the newly enforced baselines in three ways:
* contextlib / UbridgeError imports dropped as false duplicates during
conflict resolution left NameErrors in iou_vm and qemu_vm (F821);
the Dynamips router grabbed the compute-level NIOTAP instead of the
hypervisor one, breaking TAP anchoring (test failure).
* unused imports (KERNEL_UNSUPPORTED_FILTERS, UbridgeError, List) and
unsorted blocks left behind where the stack's later commits moved the
last usage elsewhere — ruff I001/F401/RUF022 now enforce these.
* mypy, now baseline-free: cache dict annotations in tc_probe, the
locators candidate list, the resolved-params None branch in the MCP
runner, and validate_bpf_syntax keeps the upstream Dict[str, Any]
return (the strict Optional[str] was wrong for the "valid" bool entry).
The e2e raw-frame probe resolves its compiler via shutil.which (S607)
and zips with strict= (B905); the remaining subprocess call is S603 by
nature and gets a per-file ignore like other test files.
ruff check/format clean, mypy at upstream parity (no new errors),
full unit suite 2167 passed.
A batch carrying the same device twice lost all but the last entry's
commands: the per-device command map kept only the last entry while
result processing iterated every original entry, so each row reported
the last entry's output under its own command list.
c3c78f99a consolidated the merge into the MCP template renderer, but
that only runs when a template is passed — the MCP direct-commands
path and gns3-copilot's direct _run path were left uncovered.
Normalize once in each tool's _run (single point covering all entry
paths, restoring the c42b3a59b coverage) and keep the renderer merge
as boundary normalization. Blocked-command info now also accumulates
across same-device entries instead of last-winning in the per-device
map.
The device tools reported failures in three shapes: topology-level
entries with only an error key, per-device entries with status 'error'
plus the reason under output (VPCS tool only), and raw exceptions
leaking out of template rendering. Every in-band error entry now
carries status 'failed' and an error message, and invalid Jinja2
templates are reported in-band instead of escaping the handler.
VPCS syntax typed into another node's console is silently discarded
(IOS answers % Invalid input) while the tool still reports success.
get_device_ports_from_topology now carries the GNS3 node type through
to callers, and VPCSCommands fails device preparation with a per-device
error unless the node type is vpcs.