Skip to content

fix: classify Direct NAT preflight failures correctly - #50

Merged
twilightsaurus merged 1 commit into
mainfrom
fix/slice-26-nat-preflight
Oct 28, 2026
Merged

twilightsaurus merged 1 commit into
mainfrom
fix/slice-26-nat-preflight

Conversation

@twilightsaurus

Copy link
Copy Markdown
Collaborator

Summary

Slice 26 PR 3 only. The automatic installer previously treated any directly-addressed DNS-preflight failure as a reason to try a DNS-only NAT candidate. That could turn missing/failing/malformed interface inspection into a misleading NAT offer.

The installer now consumes one read-only Python classification from direct-edge preflight HOST:

  • direct: valid directly-addressed topology, including matching dual-stack DNS.
  • nat-mismatch IPV4: successful interface inspection, nonlocal DNS, exactly one global IPv4 A and no AAAA.
  • nat-no-public IPV4: successful empty/private-only inspection with the same NAT DNS restrictions.
  • Other failures return nonzero with their original useful diagnostic and no classification result.

A narrow typed address-mismatch exception is raised only after successful inspection; unrelated command, JSON, address or DNS errors cannot be caught as topology mismatches. Classification uses one DNS snapshot. Bash branches only on machine tokens, never diagnostic text. The obsolete DNS-only nat-candidate CLI operation is removed; explicit dns HOST --behind-nat and provisioning behavior remain unchanged.

Installer behavior

  • Missing/unexecutable/nonzero/timed-out ip, malformed interface data and DNS failures stop with actual diagnostics and recovery guidance, without offering NAT.
  • Successful private-only/empty discovery remains eligible if DNS is a single global A with no AAAA.
  • Eligible interactive offers describe observed facts, identify NAT/externally routed topology as a possibility, and require explicit affirmation that TCP 443 is forwarded here. No is the default.
  • Noninteractive installs never prompt or silently select NAT.
  • Acceptance reruns authoritative dns --behind-nat before installer/public-edge mutation. A failed revalidation or decline leaves installation, service, credential and public-edge state unchanged.
  • Caddy ownership, TLS/certificates, raw-target routing, authentication, pairing, migration, service ownership and rollback are unchanged.

Regression coverage

  • Complete Direct-edge module: 269 tests passed. Added classification tests for matching IPv4/dual stack, nonlocal global A, private-only/empty interfaces, command exceptions/nonzero, malformed JSON/structures/fields/addresses, DNS failures/non-global/multi-A/AAAA answers, single-snapshot determinism, explicit NAT independence, and no mutation/privilege operations.
  • Extended real install.sh matrix with injected system boundaries: eligible offers only, actual diagnostics, noninteractive refusal, default/explicit decline, fresh DNS revalidation failure/success, matching Direct, and unchanged Direct/NAT upgrade/provisioning/rollback paths. Whole matrix passed.
  • The no-mutation assertion compares actual service state separately from the fixture's query log, and explicitly permits only read-only systemctl operations in that log. No ownership or acceptance guard was weakened.

Verification

  • uv run --directory services/relay pytest tests/test_direct_edge.py: 269 passed.
  • Fresh frozen bundle build: passed using pinned uv-managed CPython.
  • Complete scripts/test-installer-connections.py BUNDLE: passed.
  • Complete scripts/test-server-release.py ARCHIVE_B A/B acceptance: passed (275.5s), including frozen runtime, genuine A→B/legacy upgrades, failed-upgrade rollback, full installer acceptance and four native user-systemd flows.
  • Landlock ABI 7 and host-Python execution guard retained; 31 Python/pip targets kernel-denied.
  • Final npm run check: passed, including relay Ruff formatting/lint/mypy, docs/version checks, all suites, e2e and production build. Existing 184 relay and one TinyFugue skip remain.
  • Bash syntax and git diff --check: passed.

Scope / limitations

Seven files only: two production files, two existing test suites and three existing docs. Based on latest main/v0.4.3; no dependencies, release versions or CHANGELOG changed. Slice 25 remains Complete; Slice 26 remains In progress. PR 4 retained-edge recovery and PR 5 remaining messaging/docs remain outstanding.

All public-edge provisioning acceptance uses existing injected system boundaries, not live DNS/router/Caddy/certificate infrastructure. No live VPS, NAT VM, firewall, certificates or credentials touched. No remaining local acceptance blocker.

@twilightsaurus
twilightsaurus merged commit 996a4db into main Oct 28, 2026
5 checks passed
@twilightsaurus
twilightsaurus deleted the fix/slice-26-nat-preflight branch October 28, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant