Skip to content

feat(LinuxTentacleE2E): Phase 12.M.L.C.1 — first register E2E (Listening flavor) + slim StubSquidServer

Summary

Section C bootstrap. UNBLOCKS agent-identity coverage. Drives the real production binary's register command end-to-end through the full handshake:

  1. Binary loads/creates instance cert (TentacleCertificateManager)
  2. Binary POSTs JSON payload to stub's /api/machines/register/tentacle-listening
  3. Stub returns canned response with serverThumbprint + machineId
  4. Binary persists config to /etc/squid-tentacle/instances/<name>.config.json
  5. Binary prints registration result to stdout

Why ship-blocking

Without this E2E pin, regressions in any of the following ship silently and only surface when an operator's first register fails:

  • Register payload shape (machineName / thumbprint / roles / environments)
  • X-API-KEY header propagation
  • Config persistence path
  • ServerUrl + ServerThumbprint round-trip into persisted config (without these, run can't dial the registered server)

Architectural decision: slim Linux-specific stub

Option Choice Rationale
Reference Windows project's StubSquidServer ❌ ~600 lines (Halibut polling + cert generation + comms-test) — overkill for register-only; cross-OS project coupling smells bad
Slim Linux-specific LinuxStubSquidServer ✅ ~150 lines, register-only HTTP listener; keeps Linux project self-contained
Refactor to shared project Deferred Right long-term; defer until register failure-path / polling tests need it

Test mechanism

  • LinuxStubSquidServer.Start() → random loopback port via TcpListener(0)
  • HTTP listener responds to POST /api/machines/register/* with canned 200 + JSON body matching TentacleRegistrationClient.RegistrationResponse schema
  • ConfigureRegisterStatusCode / ConfigureRegisterBody for future failure-path tests
  • Records every received request's path + body + headers for assertion

Assertions

Layer Assertion
Exit exitCode == 0
Stub server Recorded exactly 1 register call
Path /api/machines/register/tentacle-listening (Listening flavor)
Auth X-API-KEY header present + matches --api-key arg
Payload machineName + thumbprint (cert) + roles + environments
Persistence Config file exists at /etc/squid-tentacle/instances/<name>.config.json
Round-trip Config contains ServerUrl + ServerThumbprint (so run can dial)
stdout "Registration complete" + "ServerThumbprint: <stub>"

Why HTTP (not HTTPS)

Production EnsureSchemeSafeForSecret enforcement is Warn-by-default (Rule 11), so http:// emits a warning but proceeds. No TLS cert / ACL setup needed in test fixture — operator-friendly behavior matches air-gap deployments using internal HTTP mirrors.

Fidelity tier

🟢 High (Rule 12.4): real production binary + real HTTP request + real config persistence. Only the REST endpoint is stubbed (canned response), same shape as upgrade-flow's LocalReleaseMirror.

Test class is in LinuxTentacleHostStateCollection (serializes against upgrade + install + service-fixture + binary-smoke + service-command tests — register writes /etc/squid-tentacle/).

Expected runtime: ~1-2s.

Test plan

  • Linux E2E workflow runs (manual workflow_dispatch after merge)
  • C1h_RegisterListening_PersistsConfigAndCallsServer passes within ~5s
  • No regression on existing 32 Linux E2E tests

🤖 Generated with Claude Code

Merge request reports

Loading