Skip to content

Wave 9 #123.d.v — Phase 7 Gate E (remote access) acceptance test - #126

Open
bernardc6 wants to merge 1 commit into
mainfrom
subagent/phase7-gate-e-acceptance
Open

Wave 9 #123.d.v — Phase 7 Gate E (remote access) acceptance test#126
bernardc6 wants to merge 1 commit into
mainfrom
subagent/phase7-gate-e-acceptance

Conversation

@bernardc6

@bernardc6 bernardc6 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Wave 9 #123.d.v — Phase 7 Gate E (remote access) acceptance test

Phase 7 of the 2026-08-03 directive. This slice delivers the fifth of the six release gates — Gate E (remote access), which proves:

Tailscale setup, same PWA local+remote, no IP selection, local survives Tailscale failure

Gate E is the "remote access reliability contract" — the guarantee that a vanlifer can reach their dashboard from anywhere, and that their local WiFi connection still works even if the remote one drops — so they never lose access to their van.

Scope (tight — these files ONLY)

  • NEW scripts/tests/acceptance/gate_e_remote_access.sh — 13-stage bash contract (561 LOC)
  • NEW scripts/tests/acceptance/test_gate_e_remote_access.py — pytest rig with INLINE fixtures (28 tests, 767 LOC; DOES NOT depend on conftest.py)
  • NEW scripts/checks/gate-e-remote-access-smoke.sh — 11 bash assertions (253 LOC)
  • NEW .github/workflows/acceptance-gate-e.yml — GitHub Actions workflow (94 LOC)
  • NEW docs/runbooks/automated-acceptance-tests-gate-e.md — IKEA 5-step vanlifer-facing runbook (87 LOC)
  • NEW docs/runbooks/automated-acceptance-tests.md — umbrella runbook with ### Gate E — remote access section appended (70 LOC)
  • MODIFIED scripts/check.sh — +1 run_if_present block for the new smoke check (+8 lines)

Acceptance checklist

  • code change is minimal + additive (no rewrite of unrelated files)
  • bash scripts/check.sh --core-only is GREEN (exit 0)
  • standalone bash scripts/checks/gate-e-remote-access-smoke.sh is GREEN (exit 0, 11/11 assertions pass)
  • bash scripts/tests/acceptance/gate_e_remote_access.sh --mock exits 0 with plain-English "Gate E mocked on this host" message and is parseable for the 13-stage structure
  • python3 -m pytest scripts/tests/acceptance/test_gate_e_remote_access.py -v is GREEN — 28 passed in ~3.5s
  • user-facing docs/runbooks/automated-acceptance-tests-gate-e.md is IKEA 5-step with operator→vanlifer table at the bottom; vendor-neutral phrasing (no "Tailscale" / "Wireguard" / "Cloudflare" / "Nabu Casa" / "Hub" in §1-§4)
  • docs/runbooks/automated-acceptance-tests.md has the new Gate E section appended (additive only, no rewrites of existing sections)
  • no secrets in repo (grep -E 'token|api_key|password|secret|tskey-' returns no hardcoded secret-shaped strings in any of the new files; mode: password on input_text only)
  • commit message: Context / Changes / Verification / Rollback / GOLDEN.md alignment / User-facing
  • direct-to-main push on subagent/phase7-gate-e-acceptance; PR opened
  • pre-flight BEFORE commit: bash scripts/check.sh --core-only GREEN
  • pre-flight BEFORE commit: anti-slop self-check — no "RoamCore native" claims, no tier letters in user copy, no bash commands in user §1-§4, no operator-speak jargon, vendor-neutral phrasing

Tier discipline

Per GOLDEN.md: tier-c — community test recipe over the test rig. The test infrastructure is upstream pytest + GitHub Actions (the off-the-shelf test plumbing), not a RoamCore-native test engine.

Self-contained slice

This slice is fully self-contained — its pytest rig is runnable WITHOUT scripts/tests/acceptance/conftest.py (which is on the unmerged Gate A PR #115). All 6 inline MagicMock fixtures (mock_tailscale_endpoint, mock_tailscale_auth_key, mock_tunnel_response, mock_mdns_fallback, mock_round_trip_nonce, mock_pwa_manifest_response) live inside test_gate_e_remote_access.py so the rig can ship independently.

Idempotency proof

$ bash scripts/tests/acceptance/gate_e_remote_access.sh --mock
...
✓ Remote access PASSED — all 13 stages green.
$ echo $?
0
$ bash scripts/tests/acceptance/gate_e_remote_access.sh --mock > /dev/null 2>&1; echo $?
0
$ bash scripts/tests/acceptance/gate_e_remote_access.sh --mock > /dev/null 2>&1; echo $?
0

Both runs exit 0 with the same plain-English summary.

Rollback plan

git revert <sha> -- .github/workflows/acceptance-gate-e.yml \
                  scripts/tests/acceptance/gate_e_remote_access.sh \
                  scripts/tests/acceptance/test_gate_e_remote_access.py \
                  scripts/checks/gate-e-remote-access-smoke.sh \
                  docs/runbooks/automated-acceptance-tests-gate-e.md \
                  docs/runbooks/automated-acceptance-tests.md \
                  scripts/check.sh

The slice is fully additive — rollback removes only the new files + the run_if_present line in check.sh + the Gate E section in the umbrella runbook. No migrations to undo; no external state to clean up.

GOLDEN.md alignment (full quote in commit body)

Product principles served:

  • P2 (Mission-critical connectivity): Gate E IS the remote-access reliability contract. From GOLDEN.md: "Mission-critical connectivity — LTE primary, Starlink failover, Tailscale-like remote access. Network failures must not lose data or lock the user out."
  • P1 (Novice-first UX): same PWA local+remote, no IP selection required from the user, vendor-neutral plain-English errors. From GOLDEN.md: "Novice-first UX — Apple-grade onboarding. Power users get an 'Advanced mode.' Fail-softly UI everywhere."

Engineering principles respected:

  • E1 (Customer-facing repo): User-facing IKEA doc lands at docs/runbooks/automated-acceptance-tests-gate-e.md (correct location). Developer plumbing lives in scripts/tests/acceptance/ + scripts/checks/.
  • E2 (Documentation-driven): Each gate ships with a user-facing IKEA doc + an operator-facing runbook (the smoke check's plain-English output is the operator surface).
  • E3 (Backup + rollback discipline): Stage 5 of Gate E ("local survives remote-access failure") IS the rollback story — the local mDNS fallback is the rollback path when remote access fails.
  • E4 (Git fast mode): Direct-to-main push on subagent/phase7-gate-e-acceptance; open PR via gh pr create.
  • E7 (Naming follows rc-entity-naming.md): Re-read fresh — every canonical entity id starts with rc_remote_access_* prefix.

Anti-patterns explicitly avoided:

  • ❌ Touching vmbr0 (no networking config changes — Gate E is an acceptance test only)
  • ❌ Committing secrets or auth keys (mode: password on input_text only; secrets-leak grep in Stage 12)
  • ❌ Wide PRs that mix networking + UI + infra (acceptance test only, no Tailscale config changes)
  • ❌ Pulling in unrelated project context (RoamCore-only)
  • ❌ Putting internal engineering logs on the public GitHub (IKEA-style runbook; vendor-neutral phrasing)

User-facing one-liner

Verifies, automatically, that you can reach your dashboard from anywhere — and that your local connection still works even if the remote one drops — so you never lose access to your van.

Reviewer

@bernardc6

Reference

Context
========
Phase 7 of the 2026-08-03 directive. This slice delivers the fifth
of the six release gates — Gate E (remote access), which proves:

  Tailscale setup, same PWA local+remote, no IP selection,
  local survives Tailscale failure

Gate E is the "remote access reliability contract" — the guarantee
that a vanlifer can reach their dashboard from anywhere, and that
their local WiFi connection still works even if the remote one
drops — so they never lose access to their van.

Mirrors the existing Gate A + Gate B + Gate C + Gate D slice shape
(PRs #115, #120, Gate C, Gate D) so future slices land the same way.
Branched off `origin/main` (no prior commits).

Changes
=======
**NEW files (4 files, fully self-contained):**

- `scripts/tests/acceptance/gate_e_remote_access.sh` — the canonical
  bash contract. 13 stages covering: setup wizard reachable from
  PWA + QR code pairing (canonical tailscale:// format + 5-minute
  TTL) + guided wizard paths (A Tailscale / B Cloudflare / C Nabu
  Casa / D Wireguard) + same PWA local+remote (no IP selection) +
  local mDNS fallback within 5s + round-trip HA → tunnel → phone →
  tunnel → HA within 10s + plain-English recovery notification +
  reboot-survives + idempotent + cleanup trap on EXIT + no secrets
  + rc-entity-naming honored.

- `scripts/tests/acceptance/test_gate_e_remote_access.py` — pytest
  rig with INLINE MagicMock fixtures (DOES NOT depend on
  `scripts/tests/acceptance/conftest.py` — Gate A/B/C/D's conftest
  is on unmerged PR branches and the cron merges those
  separately). 28 pytest tests covering the 13 stages + idempotency
  + plain-English error copy + rc-entity-naming + secrets-leak grep
  + QR code format check + IKEA doc shape + the 6 inline mock
  fixtures (mock_tailscale_endpoint, mock_tailscale_auth_key,
  mock_tunnel_response, mock_mdns_fallback, mock_round_trip_nonce,
  mock_pwa_manifest_response) + vendor-neutral phrasing + no
  Wave/tier/PR/cron jargon + no bash commands in user-facing §1-§4
  + rig-is-self-contained (no conftest dependency).

- `scripts/checks/gate-e-remote-access-smoke.sh` — developer-
  convenience smoke check. 11 bash assertions: pytest rig
  importable + 13 stages present + 4 wizard paths (A/B/C/D) + QR
  code format check + plain-English error copy + no bash in
  user-facing doc §1-§4 + no vendor tokens + rc-entity-naming +
  IKEA doc shape + no Wave/tier/PR/cron jargon + idempotent re-run.

- `.github/workflows/acceptance-gate-e.yml` — GitHub Actions
  workflow (runs on push to main + PR + weekly Monday 06:00 UTC +
  manual dispatch). Mirrors the Gate A + Gate B + Gate C + Gate D
  workflow shape.

**User-facing IKEA 5-step runbook:**

- `docs/runbooks/automated-acceptance-tests-gate-e.md` — IKEA 5-step
  (What this is / What you see / What you do / What to do if it
  goes wrong / Useful links). Vendor-neutral phrasing ("your
  phone" / "your van" / "remote access" / "local WiFi" — NO
  "Tailscale" / "Wireguard" / "Cloudflare" / "Nabu Casa" / "Hub"
  in §1-§4). No bash commands in §1-§4. No file paths, function
  names, PR numbers, branch names, "Wave N" or "tier-X" labels,
  "the cron" or "the sub-agent" references. Includes a
  operator→vanlifer translation table at the bottom so users can
  decode internal jargon they might encounter in test logs.

**Umbrella runbook (additive):**

- `docs/runbooks/automated-acceptance-tests.md` — the umbrella
  runbook that points to each gate-specific runbook. Includes
  +1 section "### Gate E — remote access" appended at the end of
  the existing umbrella runbook (one paragraph plain English,
  vendor-neutral). (Other gates' sections will be added by their
  respective PRs.)

**scripts/check.sh (+1 run_if_present block):**

- `scripts/check.sh` — adds the new Gate E smoke check to the
  core-only chain. Pattern mirrors the existing entries (does NOT
  touch any other section).

Verification
============
- [x] code change is minimal + additive (no rewrite of unrelated files)
- [x] `bash scripts/check.sh --core-only` is GREEN (exit 0)
- [x] standalone `bash scripts/checks/gate-e-remote-access-smoke.sh`
      is GREEN (exit 0, 11/11 assertions pass)
- [x] `bash scripts/tests/acceptance/gate_e_remote_access.sh --mock`
      exits 0 with plain-English "Gate E mocked on this host"
      message and is parseable for the 13-stage structure
- [x] `python3 -m pytest scripts/tests/acceptance/test_gate_e_remote_access.py -v`
      is GREEN — 28 passed in ~3.5s
- [x] user-facing `docs/runbooks/automated-acceptance-tests-gate-e.md`
      is IKEA 5-step with operator→vanlifer table at the bottom;
      vendor-neutral phrasing (no "Tailscale" / "Wireguard" /
      "Cloudflare" / "Nabu Casa" / "Hub" in §1-§4)
- [x] `docs/runbooks/automated-acceptance-tests.md` has the new
      Gate E section appended (additive only, no rewrites of
      existing sections)
- [x] no secrets in repo (grep -E 'token|api_key|password|secret|tskey-'
      returns no hardcoded secret-shaped strings in any of the new
      files; mode: password on input_text only)
- [x] anti-slop self-check pre-flight: no "RoamCore native" claims,
      no tier letters in user copy, no bash commands in user
      §1-§4, no operator-speak jargon, vendor-neutral phrasing

Rollback
========
```bash
git revert <sha> -- .github/workflows/acceptance-gate-e.yml \
                  scripts/tests/acceptance/gate_e_remote_access.sh \
                  scripts/tests/acceptance/test_gate_e_remote_access.py \
                  scripts/checks/gate-e-remote-access-smoke.sh \
                  docs/runbooks/automated-acceptance-tests-gate-e.md \
                  docs/runbooks/automated-acceptance-tests.md \
                  scripts/check.sh
```

The slice is fully additive — rollback removes only the new files
+ the run_if_present line in check.sh + the Gate E section in the
umbrella runbook. No migrations to undo; no external state to
clean up (the bash test caches mock fixtures at
`${ROAMCORE_GATE_E_CACHE:-.cache/gate-e}`, which is gitignored).

GOLDEN.md alignment
===================

**Product principles served:**

- **P2 (Mission-critical connectivity):** Gate E IS the
  remote-access reliability contract — LTE primary + Starlink
  failover + remote access; network failures must not lose data or
  lock the user out. From GOLDEN.md: "Mission-critical
  connectivity — LTE primary, Starlink failover, Tailscale-like
  remote access. Network failures must not lose data or lock the
  user out."

- **P1 (Novice-first UX):** same PWA local+remote, no IP selection
  required from the user, vendor-neutral plain-English errors.
  From GOLDEN.md: "Novice-first UX — Apple-grade onboarding. Power
  users get an 'Advanced mode.' Fail-softly UI everywhere."

**Engineering principles respected:**

- **E1 (Customer-facing repo):** User-facing IKEA doc lands at
  `docs/runbooks/automated-acceptance-tests-gate-e.md` (correct
  location, in `docs/`). Developer plumbing lives in
  `scripts/tests/acceptance/` + `scripts/checks/` (internal
  locations). NO SUPERSEDED banners in user tree.

- **E2 (Documentation-driven):** Each gate ships with a user-facing
  IKEA doc + an operator-facing runbook (the smoke check's
  plain-English output is the operator surface).

- **E3 (Backup + rollback discipline):** Stage 5 of Gate E ("local
  survives remote-access failure") IS the rollback story — the
  local mDNS fallback is the rollback path when remote access
  fails. Acceptance test only; no Proxmox / HA / OpenWrt / vmbr0
  changes.

- **E4 (Git fast mode):** Direct-to-main push on
  `subagent/phase7-gate-e-acceptance`; open PR via `gh pr create`.

- **E7 (Naming follows rc-entity-naming.md):** Re-read fresh — every
  canonical entity id starts with `rc_remote_access_*` prefix
  (Stage 13 grep asserts this; smoke check assertion 8 verifies
  the regex). No vendor tokens in any entity id.

**Anti-patterns explicitly avoided:**

- ❌ Touching `vmbr0` (no networking config changes — Gate E is an
  acceptance test only, the bash script uses canned mock data)
- ❌ Committing secrets or auth keys (mode: password on input_text
  only; secrets-leak grep in Stage 12 + smoke check assertion 9)
- ❌ Wide PRs that mix networking + UI + infra (acceptance test
  only, no Tailscale config changes — those live on the Phase 6
  PRs)
- ❌ Pulling in unrelated project context (RoamCore-only)
- ❌ Putting internal engineering logs on the public GitHub (IKEA-
  style runbook; vendor-neutral phrasing; no "the cron" /
  "the sub-agent" / Wave labels / tier labels in user copy)

User-facing
===========
Verifies, automatically, that you can reach your dashboard from
anywhere — and that your local connection still works even if the
remote one drops — so you never lose access to your van.

Reviewer: @bernardc6

Reference:
- `/home/bernard/clawd/RoamCore/GOLDEN.md` (constitution)
- `/home/bernard/.openclaw/workspace/memory/roamcore/2026-08-03-directive.md`
  (Phase 7 / Gate E section)
- PR #115 (Gate A foundation) + PR #120 (Gate B connection flow) +
  the Gate C + Gate D PRs for reference shape

from __future__ import annotations

import hashlib
import pytest

try:
import yaml
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