Skip to content

Development

Project Boundary

Run commands from this directory. No source or test may import another project tree or its test infrastructure.

Local Workflow

make runtime-venv
make format
make check

The runtime environment is separate from development tooling. The launcher checks its recorded runtime lock and project version but never creates or modifies the environment. Unit tests use fake programs and must not access the network or a real SSH identity.

The production launcher executes the installed module with Python isolated mode. That module derives the local file boundary from the marked project which directly owns the active venv. A global interpreter, a detached venv, the current directory, and the package's site-packages location are not accepted as substitute roots.

make test runs the ordinary parallel pytest suite with branch coverage. make host-tests runs the explicitly marked launcher cases. make check combines formatting, lint, strict typing, Bandit, tests, network and container confinement proofs, lock reproducibility, compatibility, policy, workflow, version, Python compilation, shell validation, and a network-free wheel/source archive build. The package gate installs the wheel into an isolated target and executes its declared remote-ssh-mcp entry point outside the source tree, including runtime-root and configuration validation rather than only --help.

make standalone smoke-standalone builds the current native Linux executable with the separate hash-locked PyInstaller environment, validates its ELF architecture and source/lock provenance, and then runs it from a hostile directory. The smoke proves version/help/error behavior, clean stdio EOF, and that private state is created beside the executable rather than in the current directory or PyInstaller's temporary extraction directory. CI performs this flow natively on Ubuntu 26.04 runners for both amd64 and arm64.

Render the public documentation site and validate its generated routes with:

make docs-audit

This prepares the separate hash-locked documentation environment, rebuilds site/, and checks local links and anchors, canonical URLs, sitemap forms, robots.txt, llms.txt, and deterministic output metadata. Preparing a missing environment may download its locked wheels; rendering and auditing make no network requests. Use make docs-serve for a local preview; generated site/ is never maintained source.

Published SSH Dependency

ssh-wrapper==0.1.0 is resolved from PyPI and bound by SHA-256 in every runtime-derived lock: runtime, development, standalone, and documentation. Tests and the launcher import only that installed distribution and never add an alternate source directory to PYTHONPATH.

Tests

Keep process fakes strict enough to prove argv, one authentication, mux-only reuse, bounds, and cleanup. Tests run concurrently and therefore cannot share fixed ports or mutable paths. A behavior change must be accompanied by a test that fails without it and by synchronized documentation.

Live Podman acceptance is intentionally a separate boundary and is not hidden inside ordinary unit tests. Hardware-key cases remain operator controlled and must receive key paths only at invocation time.

Live Acceptance

Prepare the explicit runtime first, then run the unattended matrix:

make runtime-venv
make live-preflight
make live-test

The automatic path generates an ephemeral Ed25519 key, creates one private Podman network, starts a confined MCP server container and a disposable SSH target, and proves command, inspection, sudo, transfer, disconnect, master-loss, and selective-cleanup behavior. The harness removes only resources bearing its per-run ownership labels.

Hardware-token acceptance keeps the MCP process on the host so OpenSSH can use the operator's normal authentication UI:

make live-fido-preflight PUBLIC_KEY=... IDENTITY_FILE=...
make live-fido-test PUBLIC_KEY=... IDENTITY_FILE=...
make live-fido-sanitized-preflight PUBLIC_KEY=... IDENTITY_FILE=...
make live-fido-sanitized-test PUBLIC_KEY=... IDENTITY_FILE=...

Every repository-owned file transfer into or out of Podman uses the bounded deterministic tar protocol in tools/container_payload.py. Build and runtime payloads place exactly this repository beneath the fixed remote_ssh_mcp/ archive prefix; bind mounts, named volumes, and podman cp are forbidden.

Debugging Silent Process Failures

When output is correct but a process hangs or disappears without a useful diagnostic, trace the real path immediately:

strace -f -e trace=execve,wait4,exit_group,kill -o trace.log <command>

An execve ENOENT identifies a missing program; exit_group(127) is the corresponding shell outcome.