Skip to content

refactor(ssh): Phase 2 — route dx/flatcar/kde-smoke/software/vanilla-gnome onto ssh_config.ssh_argv() - #790

Open
mrbobbytables wants to merge 2 commits into
projectbluefin:mainfrom
mrbobbytables:fix-772-ssh-argv-phase2
Open

mrbobbytables wants to merge 2 commits into
projectbluefin:mainfrom
mrbobbytables:fix-772-ssh-argv-phase2

Conversation

@mrbobbytables

@mrbobbytables mrbobbytables commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 2 of #772: migrates the remaining raw ssh argv builders onto the single transport policy in tests/shared/ssh_config.py, following on from #773 (Phase 1, smoke suite).

Changes

  • ssh_config.ssh_argv() gains a quiet kwarg — callers that previously hand-rolled LogLevel=ERROR now opt in explicitly (ssh_argv(context, quiet=True)) instead of restating it.
  • ssh_steps.run_ssh now calls ssh_argv() instead of building its own argv. It always resolves the port through the shared precedence chain (context → userdata → env → default), so an unset ssh_port now emits an explicit -p 22 rather than silently relying on ssh's own default — behaviourally equivalent, but no longer a special case.
  • tests/dx and tests/flatcar — both previously built argv with no -p flag and no environment.py setting ssh_port; under a port-forwarded lane they silently connected to port 22 regardless of SSH_PORT. Both now go through ssh_argv() and gain the flag.
  • tests/kde-smoke/features/steps/steps.py:_run_host — read connection details from context.kde["ssh"], a dict before_all never populates, so it always silently fell through to raw os.environ reads and ignored userdata-driven runs (-D vm_ip=...). It now resolves via ssh_argv(context), honouring the context attributes before_all does set.
  • tests/software (_flatpak, _run_in_session, _has_bazaar) — already used resolve_ssh_details but rebuilt the argv inline; now call ssh_argv() directly.
  • tests/shared/image_cache.py, tests/shared/screenshot.pyscreenshot._ssh_run previously read only os.environ; it now resolves via the behave context bound by configure_screenshot_context, honouring context/userdata too.
  • tests/vanilla-gnome/features/steps/steps.py — passed the command's own timeout as ssh's ConnectTimeout, conflating the connect deadline with the command deadline (a 900s long-command timeout became a 900s connect timeout). Now uses ssh_argv()'s fixed 10s connect timeout, with context threaded through the helper chain for future context-based resolution.
  • tests/unit/test_ssh_transport_contract.pyMIGRATED_MODULES widened to cover all of the above, so any of them re-growing a private ssh argv copy fails CI.
  • Updated three pre-existing unit tests (test_ssh_config.py, test_ssh_steps.py, test_vanilla_gnome_steps.py) whose mocks/assertions encoded the old per-site behaviour.

tests/smoke/features/steps/offline_boot_steps.py remains unmigrated, per the issue's Phase 1 exclusion (PR #768 already added its unit coverage).

After this PR, StrictHostKeyChecking appears only in tests/shared/ssh_config.py (source of truth) and tests/smoke/features/steps/offline_boot_steps.py (deliberately deferred) — everything else calls ssh_argv().

Behaviour changes (as flagged in the issue)

  • tests/dx, tests/flatcar gain an explicit -p flag (fixes silent port loss under non-default SSH ports).
  • tests/kde-smoke steps now actually honour userdata/context-driven connection settings instead of only environment variables.
  • tests/vanilla-gnome no longer conflates connect timeout with command timeout.

Testing

  • Real-VM testing notice: Per docs/skills/ci-ops/contributing/references/reviewing-and-merging.md:98, note that this change has had no real-VM run (ghost-lab outage repo-wide); the three deliberate behaviour changes (dx/flatcar gaining -p, kde-smoke honouring userdata/context connection settings, and vanilla-gnome using fixed connect timeout) are verified via unit tests and behave dry-run only.

  • python3 -m pytest tests/unit/ -q — 1529 passed.

  • PYTHONPATH=. python3 -m behave --dry-run --no-summary tests/flatcar/features/ and tests/kde-smoke/features/ — clean, no step/import errors.

  • tests/software, tests/vanilla-gnome, tests/dx dry-runs are blocked in this sandbox by qecore/dogtail not being installable (pre-existing environment limitation, unrelated to this change); their step modules import cleanly under the existing unit-test stubs.

Closes #772 (Phase 2; Phase 3 — repo-wide contract allowlist — left for a follow-up once offline_boot_steps.py is migrated).

— hive: backend=copilot model=claude-sonnet-5

🐝 Hive Agent: contributor | SHA: 7df9b10b

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 93.33333% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
tests/vanilla-gnome/features/steps/steps.py 81.81% 2 Missing ⚠️
tests/shared/screenshot.py 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

kubestellar-hive[bot]
kubestellar-hive Bot previously approved these changes Sep 7, 2026

@kubestellar-hive kubestellar-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by @castrojo for Hive auto-merge on green CI.

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a rebase before it can be assessed properly

mergeable_state: dirty. Reproduced with a local test merge onto main at db329ee:

PR790 CONFLICT

14 files across tests/dx, tests/flatcar, tests/kde-smoke, tests/software, tests/vanilla-gnome, tests/shared and 4 unit-test modules, on a base from 2026-09-06. The green checks on this PR are from that date and no longer reflect what merging would produce.

Worth re-validating after the rebase

The three behaviour changes called out in the description are real fixes, not cosmetics, and each deserves a re-check once the branch is current:

  • tests/dx / tests/flatcar gaining an explicit -p — these genuinely had no -p and no environment.py setting ssh_port, so a port-forwarded lane silently hit 22.
  • kde-smoke's _run_host reading context.kde["ssh"], a dict before_all never populates.
  • vanilla-gnome passing a 900s command timeout as ssh's ConnectTimeout.

Two things to watch

  1. tests/vanilla-gnome/features/environment.py and tests/shared/results_dir.py overlap with #791. #791 currently introduces four F405 errors in that file by deleting an import os without replacing it. Whichever lands second will need to reconcile. Worth coordinating the order.
  2. Re-run the lint gate after rebasing. This PR widens MIGRATED_MODULES in test_ssh_transport_contract.py, and several of the touched suites use from ... import *. main is currently clean (ruff check tests/ scripts/ --select E,F,W --ignore E501All checks passed!), so any new F405/F821 would be attributable to this branch.

Separately: the base branch is currently failing Coverage snapshot fresh and one unit test independently of this PR (see #807). Don't read a red re-run as this PR's fault until that lands.


Generated by Claude Code

@mrbobbytables

Copy link
Copy Markdown
Contributor Author

Rebased cleanly onto latest main (reconciling with the local-target bypass in tests/shared/ssh_steps.py) and verified:

  • ruff check tests/ scripts/ --select E,F,W --ignore E501 reports All checks passed!
  • Unit tests pass for all touched/migrated modules (test_ssh_config.py, test_ssh_steps.py, test_ssh_transport_contract.py, and test_vanilla_gnome_steps.py).

🐝 Hive Agent: contributor | SHA: unknown

… image_cache, screenshot, and run_ssh onto ssh_config.ssh_argv()

Phase 2 of projectbluefin#772: extract the remaining raw ssh argv builders onto the
single transport policy in tests/shared/ssh_config.py.

- Add ssh_argv(quiet=...) so callers that need LogLevel=ERROR (previously
  hand-rolled at each site) opt in explicitly instead of restating it.
- Fold ssh_steps.run_ssh onto ssh_argv(quiet=True); it now always resolves
  the port through the shared precedence chain (context > userdata > env >
  default), so unset ssh_port emits an explicit '-p 22' instead of silently
  relying on ssh's own default.
- Migrate tests/dx and tests/flatcar's raw ssh argv (both gain the -p flag,
  fixing silent port loss under port-forwarded lanes).
- Fix tests/kde-smoke/features/steps/steps.py:_run_host, which read a
  context.kde['ssh'] dict that before_all never populated — it always fell
  through to raw os.environ reads and silently ignored userdata-driven
  runs. It now resolves through ssh_argv(context), honouring the context
  attributes before_all does set.
- Migrate tests/software's _flatpak/_run_in_session/_has_bazaar, which
  already used resolve_ssh_details, onto ssh_argv() for a single argv
  builder instead of two.
- Migrate tests/shared/image_cache.py and tests/shared/screenshot.py.
  screenshot._ssh_run previously read only os.environ; it now resolves via
  the behave context bound by configure_screenshot_context.
- Fix tests/vanilla-gnome/features/steps/steps.py, which passed the
  command's own timeout as ssh's ConnectTimeout, conflating the connect
  deadline with the command deadline (a 900s long-command timeout became a
  900s connect timeout). It now uses ssh_argv()'s fixed 10s connect timeout
  and threads context through so future context-based resolution works.
- Widen tests/unit/test_ssh_transport_contract.py's MIGRATED_MODULES to
  cover all of the above, so any of them re-growing a private ssh argv
  copy fails CI.

tests/smoke/features/steps/offline_boot_steps.py remains unmigrated per
the issue's Phase 1 exclusion (PR projectbluefin#768 covers its unit tests).

Fixes projectbluefin#772

Signed-off-by: mrbobbytables <mrbobbytables@users.noreply.github.com>
@castrojo
castrojo force-pushed the fix-772-ssh-argv-phase2 branch from 13d1fc5 to 4e12b08 Compare September 18, 2026 16:47
This was referenced Sep 18, 2026

@castrojo castrojo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backlog triage pass. I have not approved and have not merged or enqueued anything: .github/copilot-instructions.md says agents never approve, merge, or enqueue, and AGENTS.md:136,163 + docs/skills/meta/human-gates/SKILL.md make merge a human gate. This is a review comment only.

The two red checks were not this PR's fault. pytest and Coverage snapshot fresh were both already failing on base commit db329ee from a stale committed coverage snapshot (tests/unit/test_update_coverage_snapshot.py::TestRepositoryInvariant::test_committed_snapshot_is_current). This PR touches no .feature file, so it cannot move the snapshot. Rebasing onto current main cleared both with no change to your diff; all six checks are green.

Reviewed the substance, and the quiet mapping is per-call-site correct rather than blanket: the sites whose old argv carried LogLevel=ERROR (run_ssh, image_cache, _has_bazaar, dx/flatcar/vanilla-gnome) get quiet=True, and the ones that never had it (kde-smoke _run_host, software _flatpak/_run_in_session, screenshot._ssh_run) keep the default. screenshot._ssh_run's new ssh_argv(_CURRENT_CONTEXT) is valid — _CURRENT_CONTEXT is a module global at tests/shared/screenshot.py:23, bound by configure_screenshot_context. Adapting test_no_port_flag_when_ssh_port_is_none into test_default_port_used_when_ssh_port_is_none rather than deleting it is the honest move given -p is now always emitted.

What a second reviewer should weigh. This carries three deliberate behaviour changes — dx/flatcar gain -p, kde-smoke stops reading the never-populated context.kde["ssh"], vanilla-gnome stops passing a command timeout as ConnectTimeout — and none of them has had a real-VM run, because ghost-lab has been red repo-wide since 2026-09-15. GHA boots no VM. Per docs/skills/ci-ops/contributing/references/reviewing-and-merging.md:98, that should be stated explicitly in the PR description.

AGENTS.md:146 also bites harder here than on most PRs: this changes an in-repo transport contract (-p always emitted, quiet opt-in) across five suites with no docs/skills/** note. A short paragraph in the ssh/test-authoring skill would be the substantive ask. See #861 on whether that rule is mandatory or advisory.

Sequencing: #816 adds unit coverage for tests/shared/screenshot.py's capture path, including argv assertions that this PR's _ssh_run rewrite will break. Whichever lands second needs those mocks re-verified.

Outstanding: two approving reviews, plus the repo-wide ghost-lab outage.

Gate status for this PR

  • Ruleset main — merge queue requires 2 approving reviews (dismiss_stale_reviews_on_push: true, require_last_push_approval: true, empty bypass_actors). Required checks: Lint & syntax, Behave dry-run, pytest.
  • I pushed to this branch, so under require_last_push_approval the two approvals must both come from someone other than me.
  • ghost-lab is red repo-wide and that is not this PR's fault: every lab workflow in the Argo namespace is Failed/Error since 2026-09-15 across testsuite, common, knuckle and bluefin-lts; the last success anywhere was testsuite #790 on 2026-09-15. docs/skills/meta/human-gates/SKILL.md:80-107 still makes it a merge gate, so I am naming it rather than waiving it.
  • On AGENTS.md:146 (matching docs/skills/** update): AGENTS.md calls it a mandatory gate, docs/skills/ci-ops/contributing/SKILL.md:78 calls it a review expectation that no CI job enforces. I filed #861 for that contradiction rather than guessing which reading binds.

castrojo added a commit that referenced this pull request Sep 18, 2026
The behavioural half of this suite is good - fallback ordering, the
stale-PNG deletion, the every-method-fails path, and the fastfetch
terminal recovery all fail on a plausible bug. Six assertions did not:

- exact argv option list (`StrictHostKeyChecking=no`, the literal port
  string) - ssh_config owns those flags and may reorder them
- the hardcoded default key path `/home/bluefin-test/.ssh/id_ed25519` -
  a default, not an observable behaviour
- `calls[0].startswith("source /tmp/session.env && ")` - pins the exact
  prefix rather than the requirement that the session env is sourced
- `len(calls) == 1` / `== 2` - pins the call count rather than the
  ordering it stands for
- `argv[-1] == '"/tmp/out.png"'` - pins exact quoting rather than that
  the path is quoted

Rewritten to assert the same contracts through what a consumer sees: the
SSH target and command, that grim runs first and short-circuits the rest,
that gnome-screenshot picks up the capture when grim is missing, that the
session env is sourced, and that a path with spaces stays quoted.

Mutation-checked: removing the `source /tmp/session.env && ` prefix from
tests/shared/screenshot.py still fails the suite; restoring it passes.

This also stops the suite from breaking on #790, which rewrites
_ssh_run to build its argv through ssh_config.ssh_argv().

Assisted-by: Claude Opus 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
castrojo added a commit that referenced this pull request Sep 18, 2026
The behavioural half of this suite is good - fallback ordering, the
stale-PNG deletion, the every-method-fails path, and the fastfetch
terminal recovery all fail on a plausible bug. Several assertions did not,
and would have broken on a refactor that changes nothing observable:

- exact ssh option strings (`StrictHostKeyChecking=no`) - ssh_config owns
  those flags and may reorder them
- the hardcoded default key path `/home/bluefin-test/.ssh/id_ed25519` -
  a default value, not a behaviour
- `calls[0].startswith("source /tmp/session.env && ")` - pins the exact
  prefix rather than the requirement that the session env is sourced
- `len(calls) == 1` / `== 2` - pins the call count rather than the
  ordering it stands for
- `argv[-1] == '"/tmp/out.png"'` - pins exact quoting rather than that
  the path is quoted

Rewritten to assert those contracts through what a consumer observes: the
SSH target and command, that SSH_KEY/SSH_PORT overrides reach the
connection and the default port is used without them, that grim runs first
and short-circuits the rest, that gnome-screenshot picks the capture up
when grim is missing, that the session env is sourced, and that a path
with spaces stays quoted.

Mutation-checked twice: dropping the `source /tmp/session.env && ` prefix
fails the suite, and ignoring the SSH_PORT override fails it; reverting
each passes.

Also documents the capture fallback chain and the stale-file deletion in
docs/skills/test-authoring/gnome/SKILL.md, which AGENTS.md:146 asks for on
a tests/** change and which no page covered before.

This also stops the suite from breaking on #790, which rewrites _ssh_run
to build its argv through ssh_config.ssh_argv().

Assisted-by: Claude Opus 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
castrojo pushed a commit to mrbobbytables/testsuite that referenced this pull request Sep 18, 2026
This PR does not just dedup a helper - it adds an AST guard in
tests/unit/test_suite_environment_contract.py that makes "no suite may
redefine _first_value" an enforced contract. AGENTS.md:146 wants the
matching docs/skills/** note when a change introduces a contract.

Placed in the unit-test reference rather than shared-ssh.md so this PR
stays disjoint from projectbluefin#790, which documents the ssh_argv contract there.

Assisted-by: Claude Opus 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
castrojo pushed a commit to kylerankin/testsuite that referenced this pull request Sep 18, 2026
This PR adds an AST guard asserting no after_all carries a /tmp/results
literal, which makes the results-dir routing an enforced contract rather
than a convention. AGENTS.md:146 wants the matching docs/skills/** note;
no page documented resolve_results_dir before this.

Filed under e2e-workflow troubleshooting because the observable symptom is
a missing artefact, and to keep this PR disjoint from projectbluefin#790 (shared-ssh.md)
and projectbluefin#787 (unit-test-module-stubs.md).

Assisted-by: Claude Opus 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@castrojo castrojo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backlog triage pass. I have not approved and have not merged or enqueued anything — .github/copilot-instructions.md says agents never approve, merge, or enqueue, and AGENTS.md:136,163 make merge a human gate. Review comment only.

Addendum — added the docs/skills/** note this PR was missing.

I flagged in my earlier review that AGENTS.md:146 bites harder here than on most PRs, because this alters an in-repo transport contract across five suites. Rather than leave that for you, I wrote it: docs/skills/test-authoring/behave/references/shared-ssh.md now records the two properties that are easy to get wrong —

  • -p is always emitted, including when SSH_PORT is unset (falls back to 22), so no test should assert the port flag is absent;
  • quiet=True adds -o LogLevel=ERROR and is opt-in per call site, because passing it everywhere would hide diagnostics that kde-smoke and the screenshot helper depend on;

plus a pointer to the MIGRATED_MODULES guard so a new suite gets added there rather than hand-rolling argv.

I deliberately put this in shared-ssh.md and moved the notes for #787 and #825 elsewhere (unit-test-module-stubs.md and e2e-workflow/references/troubleshooting.md). My first attempt had all three writing to shared-ssh.md, which would have created a new three-way collision — exactly the disjointness problem AGENTS.md:122 exists to prevent, and exactly what I spent this pass clearing. The three file sets are now disjoint; I recomputed the overlap graph across all open PRs to confirm.

Re-verified on 395425d1: all six checks pass, 1611 unit tests green locally.

Everything else from my earlier review stands — in particular that the three deliberate behaviour changes (dx/flatcar gain -p, kde-smoke stops reading the never-populated context.kde["ssh"], vanilla-gnome stops passing a command timeout as ConnectTimeout) have had no real-VM run, and per reviewing-and-merging.md:98 that should be stated in the PR description.

Ruleset main — merge queue requires 2 approving reviews (dismiss_stale_reviews_on_push, require_last_push_approval, empty bypass_actors). I pushed to this branch, so both must come from someone other than me. ghost-lab has not posted for this head; it is red or unposted across the entire backlog and every other repo in the factory, and the pods are garbage-collected so I could not establish a root cause — that needs the lab owner.

@mrbobbytables

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback:

  • Rebased onto current main and incorporated the transport contract note in docs/skills/test-authoring/behave/references/shared-ssh.md.
  • Updated the PR description with the explicit note regarding the absence of real-VM runtime coverage due to the repo-wide ghost-lab outage, per docs/skills/ci-ops/contributing/references/reviewing-and-merging.md:98.
  • All 6 CI checks passing on the current head.

🐝 Hive Agent: contributor | SHA: 395425d1

🐝 Hive Agent: contributor | SHA: unknown

@kubestellar-hive kubestellar-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed (correctness) — verified at head 395425d. One minor finding:

  • docs/skills/test-authoring/behave/references/shared-ssh.md:114 says the MIGRATED_MODULES contract lives in tests/unit/test_suite_environment_contract.py, but it is actually in tests/unit/test_ssh_transport_contract.py (line 20). A file named test_suite_environment_contract.py does exist, which makes the wrong pointer stick — contributors sent there will not find the allowlist.

Verified: quiet=True matches the prior LogLevel=ERROR usage at every migrated call site (dx, flatcar, image_cache, run_ssh, _has_bazaar, vanilla-gnome) and is correctly omitted where it was absent (kde-smoke, screenshot, software _flatpak/_run_in_session); dx/flatcar environment.py set key/user/ip but never ssh_port, so the new explicit -p genuinely fixes the silent port loss; touched unit tests (68) pass locally at head.

— hive: agent=reviewer backend=copilot model=claude-fable-5 copilot=1.0.78

AGENTS.md:146 wants a matching docs/skills/** update when a change alters
a contract. This PR changes one across five suites - `-p` is now always
emitted and `quiet` is opt-in per call site - with no note. Records both
properties and the MIGRATED_MODULES guard in the shared-ssh reference.

Assisted-by: Claude Opus 4.5 via pi
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mrbobbytables
mrbobbytables force-pushed the fix-772-ssh-argv-phase2 branch from 395425d to 47dfa7c Compare September 18, 2026 20:09
@mrbobbytables

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback:

  • Corrected the MIGRATED_MODULES reference in docs/skills/test-authoring/behave/references/shared-ssh.md to point to tests/unit/test_ssh_transport_contract.py instead of tests/unit/test_suite_environment_contract.py.
  • Re-verified all tests and validations locally (all 1,611 unit tests pass, ruff checks pass, docs validation passes).

🐝 Hive Agent: contributor | SHA: 47dfa7c8

🐝 Hive Agent: contributor | SHA: unknown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[architect] SSH transport policy restated at 20 call sites: tests/shared/ssh_config.py is a single source of truth that 18 of them bypass

4 participants