|
| 1 | +--- |
| 2 | +title: "Test coverage begins — and catches a real reconnect bug on day one" |
| 3 | +lede: "augment-it gets its first automated tests: Vitest wired, nine tests green across the transport, the workspace registry, and didi's corpora slab. The headline property test — 'no invoke silently vanishes' — failed first exactly as the registry predicted, and the failure was real: the client reconnect chain died on any refused connection, a live mechanism behind the zombie-session and lost-corpus symptoms. Found by the test, fixed in the transport, now guarded forever." |
| 4 | +date_created: 2026-07-30 |
| 5 | +date_modified: 2026-07-30 |
| 6 | +publish: true |
| 7 | +authors: |
| 8 | + - Michael Staton |
| 9 | +augmented_with: |
| 10 | + - Claude Code on Claude Fable 5 |
| 11 | + - Claude Code on Claude Opus 4.8 |
| 12 | +files_changed: |
| 13 | + - packages/workspace/src/transport.ts |
| 14 | + - packages/workspace/test/transport.test.ts |
| 15 | + - packages/workspace/test/workspace-socket-test-server.ts |
| 16 | + - packages/workspace/vitest.config.ts |
| 17 | + - services/workspace/test/workspaces.test.ts |
| 18 | + - services/workspace/test/chat-corpora-slab.test.ts |
| 19 | + - services/workspace/src/chat.ts |
| 20 | + - context-v/specs/Corpora-Builder-Harmony-Test-Registry.md |
| 21 | +--- |
| 22 | + |
| 23 | +# Test coverage begins — and catches a real reconnect bug on day one |
| 24 | + |
| 25 | +## Why Care? |
| 26 | + |
| 27 | +For the entire life of augment-it, agents have written and rewritten this |
| 28 | +codebase with **zero automated tests** guarding it — verification was |
| 29 | +typechecks, builds, hand-run prove-scripts, and the operator walking the |
| 30 | +surface. That debt came due this month: humain-vc's corpora failing to |
| 31 | +load, corpus creations that never landed, a rail that renders "empty" |
| 32 | +indistinguishably from "broken." We couldn't tell a bug from an empty |
| 33 | +workspace because nothing asserted the difference. |
| 34 | + |
| 35 | +This is the first automated test coverage in the repo. And it earned its |
| 36 | +keep immediately: the very first property test we wrote failed on its |
| 37 | +first run — as the registry spec explicitly predicted it would — and the |
| 38 | +failure was not a test artifact. It was a **real reconnect bug in the |
| 39 | +client transport** that plausibly underlies the zombie-session and |
| 40 | +"invokes never reach the workspace" symptoms the operator has been |
| 41 | +hitting. The test found it; the fix closes it; the test now guards it. |
| 42 | + |
| 43 | +## The bug the test caught |
| 44 | + |
| 45 | +The client transport (`packages/workspace/src/transport.ts`) reconnects |
| 46 | +to the workspace-service after a dropped socket. But it only scheduled |
| 47 | +that reconnect from the WebSocket **`close`** handler. It turns out that |
| 48 | +when a connection is *refused* — the exact situation during a |
| 49 | +workspace-service restart or redeploy, when the browser's reconnect |
| 50 | +attempt hits a port with nothing listening — Node's native WebSocket |
| 51 | +(and, per spec, browsers too) fires **only `error`, never `close`**. |
| 52 | + |
| 53 | +So the reconnect chain died on the first failed attempt. A surface that |
| 54 | +lost its socket during a backend restart would never reconnect on its |
| 55 | +own; it stayed wedged until a full page reload. Invokes fired into that |
| 56 | +window hung to their 120-second deadline and then blamed the server — |
| 57 | +precisely the "the workspace did not reply" mystery. This is a strong |
| 58 | +candidate for one of the mechanisms behind |
| 59 | +[[Search-And-Add-Invokes-Never-Reach-The-Workspace]] and the |
| 60 | +zombie-session class. |
| 61 | + |
| 62 | +The fix: an `error` on a socket that never opened now runs the same |
| 63 | +reconnect tail the `close` handler does, guarded by a per-attempt flag so |
| 64 | +a browser that fires *both* `error` and `close` still schedules exactly |
| 65 | +one reconnect. Established-socket drops, auth-death (4401/4403), and |
| 66 | +deliberate `close()` all keep their existing behavior untouched. |
| 67 | + |
| 68 | +Visible proof it works: the property test's whole suite dropped from |
| 69 | +2.1s to 0.9s after the fix, because the invoke now reconnects promptly |
| 70 | +instead of hanging to its deadline. |
| 71 | + |
| 72 | +## What landed |
| 73 | + |
| 74 | +### The harness |
| 75 | + |
| 76 | +Vitest is wired as the repo's test runner (unit + service tiers; |
| 77 | +Playwright for the future E2E tier per the plan). `pnpm test` runs the |
| 78 | +repo's turbo `test` task; each package runs `vitest run`. No new runtime |
| 79 | +dependencies — the only additions are the test library itself. In |
| 80 | +particular, the transport tests needed a WebSocket server to connect to, |
| 81 | +and rather than pull the `ws` package back in (deliberately removed from |
| 82 | +this repo), the harness includes a small hand-rolled RFC-6455 endpoint |
| 83 | +over `node:http` — zero dependencies, mirroring the real |
| 84 | +workspace-service invoke/claim contract. |
| 85 | + |
| 86 | +### Group C — transport resilience (4 tests, `packages/workspace`) |
| 87 | + |
| 88 | +The registry's ✓-phrases, now green: no invoke silently vanishes across |
| 89 | +every socket-churn scenario (the one that caught the bug); a pre-open |
| 90 | +invoke is delivered exactly once after open; close 4401 fails all pending |
| 91 | +work immediately with "session expired"; and after auth-death the |
| 92 | +transport tries one silent refresh-reconnect, then waits — no storm. A |
| 93 | +timing seam was added to `transport.ts` so minutes-scale deadlines and |
| 94 | +backoff are assertable in milliseconds (production defaults unchanged). |
| 95 | + |
| 96 | +### Group D — workspace registry (3 tests, `services/workspace`) |
| 97 | + |
| 98 | +The `default_domain_type` resolution that suspect 1 hangs on — |
| 99 | +humain-vc's `thesis` vs the `strategy` fallback — plus `workspace.json` |
| 100 | +org_id winning over the `WORKSPACE_ORG_MAP` env fallback, and a |
| 101 | +.env-less workspace still listing with `has_env: false`. Each test builds |
| 102 | +a throwaway `clients/` root on disk and loads the module fresh, because |
| 103 | +the env map parses once at import. |
| 104 | + |
| 105 | +### Group H — didi's corpora slab (2 tests, `services/workspace`) |
| 106 | + |
| 107 | +The "Existing corpora" slab didi reads before resolving a corpus name: |
| 108 | +it lists every domain in the workspace regardless of type (the |
| 109 | +load-bearing difference from the curator rail's typed query), and it |
| 110 | +degrades to an empty slab — never a failed turn — when the resolver |
| 111 | +hiccups. Capability dispatch and NATS are mocked, so the contract is |
| 112 | +pinned without a live bus. |
| 113 | + |
| 114 | +## Where this sits |
| 115 | + |
| 116 | +Nine tests, three of the registry's ten MECE groups. Groups A (identity |
| 117 | +contract, ExUnit in id-didi-sh), B (session tenancy), E (canonical CRUD), |
| 118 | +F (corpus files), G (curator state), I (end-to-end), and J (the |
| 119 | +DB↔disk↔UI alignment audit) remain proposed in the registry, written as |
| 120 | +the ✓-phrases we want to see turn green next. The registry spec |
| 121 | +([[Corpora-Builder-Harmony-Test-Registry]]) tracks every one, Proposed or |
| 122 | +Implemented, with its purpose and location. |
| 123 | + |
| 124 | +## See also |
| 125 | + |
| 126 | +- [[Corpora-Builder-Harmony-Test-Registry]] — the living test registry |
| 127 | +- [[Test-Coverage-Harness-And-Regression-Floor]] — the harness plan |
| 128 | +- [[Troubleshooting-Workspace-DB-State-Alignment]] — the live bug hunt this serves |
| 129 | +- [[Search-And-Add-Invokes-Never-Reach-The-Workspace]] — the open issue this fix likely closes |
| 130 | +- [[Session-Expiry-Turns-The-App-Into-A-Zombie]] — the zombie class the reconnect bug feeds |
0 commit comments