Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
7.9 KiB
id, title, status, wave, depends_on, plan, requirements, acceptance_criteria, system_design
| id | title | status | wave | depends_on | plan | requirements | acceptance_criteria | system_design | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-e2e-and-docs | Container E2E coverage and operator documentation | done | 6 |
|
plan.md |
|
|
|
Task 08: Container E2E Coverage and Operator Documentation
Summary
Prove the assembled capability against a real sshd target while traffic to port 22 is blocked and restored, covering the one thing no unit test can: that both surfaces flip in the right direction at the right probe. Document the single new operator configuration key.
In scope
apps/web/e2e/tests/ssh/reachability.spec.tsunder thecontainersproject, which already gates real-SSH scenarios onKANDEV_E2E_CONTAINERS=1and owns thekandev-sshd:e2eimage. Four flows:- A reachable host renders the settings panel row as reachable.
- Traffic to the sshd container is blocked; after the failure threshold, the panel reports the host unreachable by name.
- Traffic is restored; the next probe clears the panel on a single success, with no confirming probe.
- A launch started while the host is stopped is attempted and fails with a message naming the host, rather than being refused.
- The failure threshold is driven by repeated Probe now requests. These requests use the same store path as the scheduled poller and avoid a wall-clock dependency in the test.
docs/public/configuration.mdgainsexecutors.sshReachabilityIntervalSeconds/KANDEV_EXECUTORS_SSHREACHABILITYINTERVALSECONDS: default, supported range, the meaning of0, and the clamp behavior.docs/public/executors.mdgains a short SSH reachability section describing what the probe answers, what it deliberately does not do (it never gates a launch and never repairs a moved host), and where the result appears.
Out of scope
- Any production code change. If a flow fails, the fix belongs in the work order that owns the behavior, not here.
- Generic QA, review, or full-verification sweeps.
- Non-container E2E projects. Every flow here needs a stoppable real host.
- Screenshots or media capture.
Acceptance
- The stop flow observes the settings panel naming the host as unreachable, and does so only after the threshold is reached, not on the first failed probe.
- The restart flow observes the panel clearing on a single successful probe.
- Both public documentation pages describe the key and the capability's deliberate limits, and the docs build passes.
Verification
# From apps/web (requires a running Docker daemon):
KANDEV_E2E_CONTAINERS=1 pnpm e2e:run --project=containers tests/ssh/reachability.spec.ts
# Documentation:
pnpm run lint
Do not pass all-worker overrides: local runners enforce one worker per shard
and a memory-aware shard budget, and the containers project's
worker-scoped backend depends on that serialization.
Files likely touched
apps/web/e2e/tests/ssh/reachability.spec.tsapps/web/e2e/pages/SSHSettingsPage.tsapps/web/e2e/helpers/ssh.tsdocs/public/configuration.mddocs/public/executors.md
Dependencies
Tasks 05 and 07. The launch flow needs the launch-path attribution and the pre-launch warning.
Risks
- These specs cannot run on a host without a Docker daemon, so they are the one part of the work package that a contributor may not be able to execute locally. Every acceptance criterion in the plan except the four flows here has non-E2E evidence, so a blocked run does not blind the rest of the package.
- Stopping a container mid-test makes timing part of the assertion. Drive the threshold through the configured interval rather than through fixed waits, or the spec becomes the slowest and flakiest file in the project.
docs/public/**is website-ready user documentation; a configuration key documented with the wrong default or a missing0semantic is worse than an undocumented one, because an operator will trust it.
Parallelism
sequential
Inputs
- Plan sections E2E tests and Tests.
apps/web/e2e/README.mdand thecontainersproject definition ine2e/playwright.config.ts.e2e/helpers/ssh.tsande2e/fixtures/ssh-image.tsfor the sshd container lifecycle.docs/public/configuration.mdfor the existing key-documentation shape.
Results
Implemented apps/web/e2e/tests/ssh/reachability.spec.ts under the
containers project, extended apps/web/e2e/pages/SSHSettingsPage.ts with
reachability-card locators and a probeNow() helper, and documented
executors.sshReachabilityIntervalSeconds /
KANDEV_EXECUTORS_SSHREACHABILITYINTERVALSECONDS in
docs/public/configuration.md plus a new "Reachability" section in
docs/public/executors.md. apps/web/e2e/helpers/ssh.ts needed no change;
dropTrafficToPort22/restoreTraffic already existed from concurrency.spec.ts.
Two deliberate deviations from this work order's literal text, both verified against real backend source before writing:
- Threshold driven by repeated "Probe now" clicks, not a short configured
poller interval.
httpProbeReachability→Poller.ProbeAndWaitruns the exact samestore.Observehysteresis path (consecutive_failuresincrement, threshold-gated state flip, immediate clear on success) that the scheduled poller would, and the route is synchronous, so a click-then-await drives the identical production logic with zero wall-clock dependency. This more directly satisfies this file's own Risk ("timing part of the assertion... becomes the slowest and flakiest file in the project") than configuring a short interval and waiting on ticks would have. - The launch-failure flow needed a far larger timeout budget than a
dial-timeout calculation predicts, discovered by running the spec for
real, not by reasoning from source alone. The client's own
sshDialTimeoutis 30s, so the first version of the launch-failure assertion used a 60s poll (matchingerror-surfacing.spec.ts's equivalent assertion). It reliably timed out with the session stillCREATED. A debug run withE2E_DEBUG=1showed the real failure surfacing only after ~148s, with the client-side error itself an SSH handshake EOF (ssh: handshake with 127.0.0.1:<port>: ssh: handshake failed: EOF), not a dial timeout — meaning the TCP connect to the published port succeeds (completed against Docker's own port-publishing plumbing) before the connection stalls and is eventually torn down. That stall, not the client's dial budget, dominates the wall-clock cost, and its length is a property of the host's Docker networking mode rather than of this codebase's own configured timeouts. The assertion's poll timeout is now 200s (test timeout 240s) to cover that path with margin rather than one tuned to a fast local failure (error-surfacing.spec.ts's missing-binary case, which fails almost immediately and never pays this cost).
Verification
$ cd apps/web && pnpm run lint
> @kandev/web@0.1.0 lint
> eslint --max-warnings 0
(clean, exit 0)
$ node scripts/validate-public-docs.mjs
Validated 46 published docs pages.
$ cd apps/web && KANDEV_E2E_CONTAINERS=1 pnpm e2e:run --project=containers tests/ssh/reachability.spec.ts
✓ ssh executor — reachability › reflects reachable, then unreachable after the failure threshold, then clears on a single success
✓ ssh executor — reachability › a launch started while the host is unreachable is attempted and fails naming the host, not refused
2 passed