2026-05-30 — SRP cleanup: drop Remote Terminal VM helpers from api/services/network.py¶
Motivation¶
api/services/network.py was 425 lines and its
Responsibility: line read
Idempotent helpers for resource group + VNet + NSG + Public IP
— a four-noun chain that explicitly described four cloud primitives in one
module. That wording violated the charter §11 SRP gate ("If the
Responsibility line needs 'and' chains, unrelated nouns, or more than one
architectural layer (route + service + task + parser), split the work before
adding more code"), and it described a world that no longer exists.
The VNet / NSG / Public IP / NIC / SSH-rule helpers were originally written
for the Remote Terminal VM topology — compute.run_shell(),
ensure_ssh_from_function_app(), and friends, see
docs/features_change/2026-05/2026-05-13-primer-design-run-command.md
and docs/features_change/2026-05/2026-05-13-terminal-ssh-hardening.md
for the historical use site. That topology was retired when the browser
terminal moved to a Container App sidecar in
docs/container-apps-migration.md and
charter §14 made the Remote Terminal VM model explicitly out of scope:
Any new Remote Terminal VM, NSG, public IP, SSH path, or admin password handling. The browser terminal is a sidecar.
A workspace grep for every exported symbol confirmed only
ensure_resource_group had a live caller
(api/routes/resources.py line 24 +
line 61); the other seven (NetworkInfo dataclass, _dns_label,
ensure_network, create_ssh_rule, ensure_ssh_from_function_app,
delete_resource, delete_resource_group) had zero consumers in
api/, terminal/, web/, scripts/, or infra/ — only the legacy
change-note prose and the Key entry points: header inside network.py
itself.
User-facing change¶
None. Pure dead-code removal + module rescope. No HTTP route, no Celery
task, no Bicep, no UI control is affected. api/routes/resources.py still
calls network_svc.ensure_resource_group(...) and its behaviour is
byte-for-byte unchanged.
API / IaC diff summary¶
- api/services/network.py: 425 → 40 lines.
- Removed (dead code):
NetworkInfodataclass,_dns_label,ensure_network,create_ssh_rule,ensure_ssh_from_function_app,delete_resource,delete_resource_group, plus all the constants that fed them (VNET_NAME_TEMPLATE,SUBNET_NAME,NSG_NAME_TEMPLATE,PIP_NAME_TEMPLATE,NIC_NAME_TEMPLATE,MAX_FUNCTION_SSH_SOURCE_IPS) and the now-unusedhashlib/re/network_clientimports. - Kept:
ensure_resource_group(credential, subscription_id, resource_group, region)— the only live consumer at api/routes/resources.py:61. - Rewrote the module docstring so
Responsibility:is a single sentence ("Idempotentresource_groups.create_or_updateshim …") andEdit boundaries:explicitly steers future networking primitives to a dedicated module rather than back intonetwork.py. - No route, task, test, Bicep, frontend, persona-matrix, or env-var change.
Validation evidence¶
- Wide:
uv run pytest -q api/tests→ 2151 passed, 3 skipped in 36.49s on the new tree. - The one
test_terminal_exec.py::test_run_truncates_stdout_above_capfailure under-n autois a pre-existing flake on the parallelxdistworker (subprocess timeout race); re-run in isolation (uv run pytest -q api/tests/test_terminal_exec.py::test_run_truncates_stdout_above_cap) → 1 passed in 10.13s. Not related to this SRP refactor and repro'd on the previous commit too. - Lint:
uv run ruff check api/services/network.py→ All checks passed!. - Consumer search:
grep_search "from api\.services import network|from api\.services\.network|api\.services\.network"→ only api/routes/resources.py:24.grep_search "ensure_network|create_ssh_rule|ensure_ssh_from_function_app|delete_resource_group|delete_resource\(|NetworkInfo"→ all 14 matches were insideapi/services/network.pyitself (docstring +Key entry points:line + the defs being removed). Theensure_ssh_from_function_appmatches outsideapi/are indocs/features_change/2026-05/*.mdand the prebuiltsite/HTML that mirror those notes — historical narrative only, not live code.- No test fixture / mock / OpenAPI payload references the removed symbols.
- Frontend: no
web/src/**files touched —npm run buildnot required. - IaC: no Bicep touched —
azd provision --previewnot required. - Diff audit:
git status --short→ onlyM api/services/network.py;git diff --stat→ 1 file, +14 / -399.
Hardening discipline (§12a):¶
- In scope: SRP refactor (dead-code removal in
api/services/) — no auth, RBAC, network ACL, JWT, ticket, CORS, or sanitisation surface changed. - RBAC change is single-PR safe (no role narrowed) — N/A, no RBAC change.
- Persona Matrix tests pass for owner / contributor / reader / dev_bypass — wide-sweep green; the removed helpers were never invoked from any route exercised by the matrix.
- Reader allowlist unchanged — no Reader-required route touched.
- Capability Probe passes locally — no new Azure surface, probe unaffected.
- New guard ships default-OFF behind
STRICT_*/ENFORCE_*env var — N/A, this PR removes code, it does not add a guard. - No
Depends(require_caller)added to an SSE event stream — no SSE changes. - Change note (this file) summarises persona impact: every persona is byte-for-byte unaffected. The dead helpers had no caller, so there is no observable behaviour to preserve or break for any persona.