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:
- Binary loads/creates instance cert (
TentacleCertificateManager) - Binary POSTs JSON payload to stub's
/api/machines/register/tentacle-listening - Stub returns canned response with
serverThumbprint+machineId - Binary persists config to
/etc/squid-tentacle/instances/<name>.config.json - 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-KEYheader propagation - Config persistence path
-
ServerUrl+ServerThumbprintround-trip into persisted config (without these,runcan'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 viaTcpListener(0) - HTTP listener responds to
POST /api/machines/register/*with canned 200 + JSON body matchingTentacleRegistrationClient.RegistrationResponseschema -
ConfigureRegisterStatusCode/ConfigureRegisterBodyfor 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
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_dispatchafter merge) -
C1h_RegisterListening_PersistsConfigAndCallsServerpasses within ~5s -
No regression on existing 32 Linux E2E tests