AIDE Architecture — Phase 4: Wiring (Contracts + Events + E2E)
SkillWeb & browsingPhase 4 SOP for the AIDE offline-first IDE rebuild, the contract layer between frontend and daemon: OpenAPI generated from zod schemas, shared fixtures consumed by BOTH sides' tests (drift-proofing), WebSocket/SSE event channel (logs, diagnostics, model status, training stream), Playwright e2e (auto-retrying assertions, isolated contexts, WS mocking), and the offline-first egress audit. Use whenever a new route/event is added, a contract test fails, frontend/backend disagree on shapes, or e2e flakiness is being debugged. Research-grounded (api-contract-testing.com, OpenAPI, Playwright docs, WebSocket spec).
Use AIDE Architecture — Phase 4: Wiring (Contracts + Events + E2E) in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add AIDE Architecture — Phase 4: Wiring (Contracts + Events + E2E) and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the AIDE Architecture skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by anonymousnomad/covert-coder in skills/packs/aide-arch-wiring/SKILL.md and read by Ahel’s review.
Doctrine
- The contract is the law. Every frontend/backend boundary: request/response schemas in
common/contracts/, zod.strict()at BOTH edges (daemon validates in, client validates out), OpenAPI generated from the schemas, and contract tests on SHARED fixtures so drift fails CI, not users. - Offline-first, opt-in online: the wire is 127.0.0.1 only. WS upgrade paths and egress are auditable; any remote endpoint requires an explicit opt-in flag persisted in config, shown in the UI.
- Verify before claiming: e2e suite green + live browser check before a phase is done.
What
Phase 4 wires everything already built into one verified system:
- OpenAPI doc:
common/contracts/*.ts→ generatedopenapi.jsonvia zod 4 NATIVEz.toJSONSchema(schema, { target: 'openApi3' })(see Known issues: zod-to-json-schema does NOT support zod 4 schemas). Serves as the human-readable spec AND the machine contract. - Contract tests: for every route, a fixture (valid + invalid payloads, error cases) in
tests/fixtures/consumed by: (a) daemon route tests (node --test + supertest-style against the real router), (b) frontend api client tests (node --test with fetch mock), (c) e2e assertions (Playwright, real browser + real daemon). - Event channel (WebSocket):
wson the daemon; typed events (channel + payload schema per event) — e.g.model:status,log:line,diagnostics:publish,training:progress,file:changed(external watcher, Phase 7). Frontend event bus subscribes per view; auto-reconnect with backoff; events are NEVER used for request/response (REST stays request/response — WS is one-way push only). - Playwright e2e: real daemon + vite preview + real Monaco; user-visible flows: open file, edit, save, find, replace workspace, split, model chat, git commit (Phase 7), training start/stop (Phase 10).
- Egress audit: boot-time check in dev mode (and a lint rule) that flags any fetch not to 127.0.0.1; a
config.onlineFeaturesmap with explicit opt-in keys.
How
1. OpenAPI generation + drift test
- One script:
npm run contracts= reads all schemas (tagged with@routemetadata: method, path, response code) → emitscommon/openapi.json+ TypeScript types are already z.infer (no codegen for types). - Drift test:
node --testrunsnpm run contractsinto a temp file and fails if the committedopenapi.jsondiffers. Schema change without doc update = red test = caught.
2. Contract tests (the drift-proofing core)
tests/fixtures/file.ts # e.g. readValid: {path:'src/a.ts'}, readTooLarge, readForbidden('..\x'), writeValid, replaceValid…
tests/contracts/file.test.ts # runs the daemon router against every fixture, asserts envelope + schema
tests/browser/api.file.test.ts # runs the api client against mocked fetch returning the SAME fixture payloads
- Rule: a fixture added for the daemon MUST be added for the client in the same commit (the test that fails when this rule is broken: a grep/audit test over fixture keys — keep it simple: one shared
fixtures/index.tsexporting both, so the client test file literally imports the same objects). - Error-path fixtures mandatory (FORBIDDEN, NOT_FOUND, PAYLOAD_TOO_LARGE) — error envelope shape is part of the contract.
3. WS event channel
- Protocol:
ws://127.0.0.1:<daemonPort>/ws; client sends{ type:'subscribe', channels: [...] }; server pushes{ channel, ts, data }where data validates against the channel's zod schema server-side before send (fail closed: invalid event payload = server bug, never sent). - Channels (Phase-scoped):
model(status/warmup transitions),log(daemon log tail for the RUN view),diagnostics(LSP publish → editor markers — this REPLACES the current poll/diagnostics-clear GETs where possible),training(progress/lines),chat(streaming tokens for chat, Phase 6 — SSE alternative: chat may prefer SSE for simplicity; decide per stream: WS for multi-channel daemon state, SSE for single long streams like chat/training logs. Either way the event SCHEMAS live in common/contracts/events.ts). - Reconnect: exponential backoff (1s → 30s cap), resubscribe on reconnect, buffer nothing (state is re-fetched via REST on reconnect — WS is push, REST is truth).
- Frontend event bus:
subscribe(channel, handler)returns unsubscribe; views MUST unsubscribe on teardown (leak rule from Phase 2).
4. Playwright e2e
- Config:
webServerarray starts daemon (node node/src/server.tson 4778) +vite preview(built assets, 4173);reuseExistingServer: true;use: { baseURL }; workers: 1 +test.describe.configure({ mode: 'serial' })(daemon is stateful — parallel workers fight over the same workspace). - Debugging swallowed errors: boot paths (e.g. session restore) catch silently — when a flow "does nothing", run a TEMPORARY debug spec that logs
console,pageerror,request/responsefor/api/and re-fetches state from the page itself; the answer is in the request log (found:path=%5Ce2e-scratch.txt403 → leading-backslash uriToRelPath bug). Delete the debug spec before pushing (it is type-checked by the node tsconfig). - Assertions: auto-retrying (
expect(locator).toHaveText(...),toBeVisible()); avoid fixed sleeps;page.waitForResponse('**/api/**')for contract timing. - WS in tests:
page.evaluateto read the event bus state, or mock WS (page.routeWebSocket) for failure-path tests (reconnect, disconnect UX). - Every e2e flow asserts the ENVELOPE too (a toast with an error code is an assertion target, not a flake).
- Offline e2e rule:
--offlinemode flag for the daemon that makes the egress audit FAIL any remote fetch during the run — the offline e2e suite runs with it on.
5. Egress audit
browser/src/services/egress.ts: wraps fetch — allows same-origin +127.0.0.1:*only unlessconfig.onlineFeatures.<key> === true. In dev, logs a loud warning for any denied egress (CDN font/icon slip = caught at first boot).- Lint: ESLint
no-restricted-globalsonfetchoutside api.ts/egress.ts (imports of api client are the only legal path).
Why (research grounding)
- api-contract-testing.com: schemas at the runtime edge, single source of truth, contract tests against shared fixtures — this is the mechanism that makes "frontend/backend can never drift" a testable property instead of a hope.
- OpenAPI: the cross-language documentation + validation standard; generating it from zod means docs can't rot separately from code.
- Playwright docs: auto-retrying assertions and isolated contexts are the documented anti-flake design; WS mocking (
page.routeWebSocket) is first-class for failure-path tests. - WS vs SSE (WebSocket spec + practice): WS = bidirectional, multi-channel push; SSE = one-way server→client streams. Use the right tool per stream; unify SCHEMAS regardless of transport.
- Offline-first: egress audit is the enforcement mechanism for the user's core requirement (offline by default, online opt-in).
Dependencies
ws (daemon), zod ^4 (shared), Playwright (dev only — chromium cached in %LOCALAPPDATA%\ms-playwright, install once, never re-download per run), vite preview (e2e target; preview AND server must proxy /ws with ws: true). Node 26 on this machine: node-side TS must be erasableSyntaxOnly (no parameter properties). npm run check:arch uses --test-concurrency=3 (see Known issues: Node 26 parser flake under parallel load).
Threat matrix
| Threat | Impact | Mitigation |
|---|---|---|
| Frontend/backend schema drift | silent data corruption, 500s in prod | zod .strict() at both edges + shared fixtures in tests/fixtures/index.ts imported by BOTH sides' tests + openapi drift test (scripts/contracts.mjs regen vs committed common/openapi.json) |
| API client calls wrong route/method | feature silently dead (save never lands) | mock-based client test MUST assert seen[0].method AND seen[0].url for EVERY api method (real bug hit: fileWrite posted to /api/file instead of /api/file/write) |
| URI→relPath mangles Windows paths | 403 containment, restore fails, session wiped | file:///x → strip scheme then ONE leading /, THEN map /→\; regression-tested in e2e session-restore |
| Failed restore wipes saved session | user data loss (silent) | per-tab try/catch in restoreSession (one bad tab ≠ dead restore) + only session.set() when openPaths().length > 0 |
| Opaque view overlay intercepts editor clicks | UI unclickable (split/search buttons) | editor activity with NO overlay as default; re-clicking an active activity toggles back to editor. NOTE: Playwright toBeVisible() passes for elements BEHIND opaque overlays (visibility ≠ hit-testability) — real clicks expose it |
| CI-only failure (Windows vs Linux) | red CI, green local | path-escape tests must use PORTABLE separators (../../etc/passwd), never backslashes; before push, run npm run check AND node scripts/ci-run-all.mjs AND the e2e suite |
| Unverified push to GitHub | silent CI breakage on the protected branch | after EVERY push: git rev-parse HEAD vs git ls-remote origin main MUST match, then poll Actions runs API until our SHA's run concludes success (see how-to: no gh CLI — git credential fill → password = PAT → Authorization: Bearer for runs/jobs/logs endpoints; anonymous = 403) |
| WS event payload rot | fail-closed server errors | event send-sites parse against the channel's zod schema before emit; one fixture per event covered by tests |
| Reconnect stampede on daemon restart | boot connection storm | exponential backoff 1s→30s + jitter; resubscribe after reconnect; state re-fetched via REST (WS = push only, REST = truth) |
| Browser-side fetch to the internet | breaks offline-first | egress.ts wrapper (same-origin + 127.0.0.1 only) + ESLint no-restricted-globals banning raw fetch outside api.ts/egress.ts |
| Over-parallelized node --test | Node 26 TS parser crash (ERR_INTERNAL_ASSERTION: unreachable in parseTypeScript, especially under AV) | --test-concurrency=3; if the parser crash returns, lower it or add --test-force-exit only as a stopgap |
| Port contention between suites | EADDRINUSE flakes (editor-smoke vs manually-started preview) | never run dev servers alongside ci-run-all/e2e; Playwright reuseExistingServer: true but prefer killing stray 4173/4778 listeners first |
Known issues / bugs (watch these)
- zod-to-json-schema REMOVED: 3.25.2 declares zod ^4 only as a PEER; its README (line 386) states zod 4 schemas are NOT supported. zod-openapi is unusable with zod 4. VERIFIED WORKING: zod 4 native
z.toJSONSchema(schema, { target: 'openApi3' })— the OpenAPI emitter (node/src/openapi.ts) uses it; regenerated vianpm run contracts. - SessionStore is DISK-based (load()/save() read/write
session.jsonper call — no in-memory cache): e2ebeforeEachdeleting.aide/session.jsonIS sufficient to reset state; no daemon restart needed. - Status bar contract: health text (
daemon <version>) is overwritten by the first onTabChange (readywhen no tabs) — e2e assertions must match/daemon|ready/, not exact text. - E2E overlay dance: file-list clicks need the MAP activity first; tab assertions use the tab's BASENAME as text; title is
AIDE — <workspace>. - Drift test false positives: OpenAPI emitter ordering (sort schemas/keys) — nondeterministic output causes red tests with no real change. Sort all emitted keys.
- Zod strict vs OpenAPI optionality:
.optional()fields must map to OpenAPInullable/optionalcorrectly or the doc lies — drift test + fixture withundefinedfield catches it. - WS message size: giant log lines (training dumps) — chunk server-side or cap per-event (e.g. 256KB) with a
truncatedflag; don't blow the browser's WS buffer. - Reconnect storms: on daemon restart, EVERY view resubscribes at once — jitter the backoff (
+ random(0,1s)) or the daemon gets a connection stampede on boot. - E2E state bleed: tabs/open files persist in the daemon session across test runs — e2e resets by deleting
.aide/session.json+ scratch files inbeforeEach(disk-based store makes this sufficient). - Vite preview + WS proxy: preview server must proxy WS upgrades too, or e2e against
vite previewloses the event channel — configured:server.proxyANDpreview.proxyboth with/ws→ws: true. - Playwright browser download: offline machine —
npx playwright installonce, cache kept; e2e must never run an install implicitly. - Event schema rot: an event schema changed in common/ but the daemon's send-site wasn't updated — the send-site parse (fail closed) makes this a loud server error, good; make sure the test suite covers each event once with a fixture.
- DAP timing:
dap-manager.request(id, request, timeoutMs = 15000)— the DAP fixture passes 60s via anask()helper; 15s can flake under load on this machine. - CI log access: Actions logs API is 403 anonymous and there is no
ghCLI — use the stored credential ("protocol=https\nhost=github.com\n\n" | git credential fill→password=line) asAuthorization: Bearerfor runs/jobs/logs endpoints. NEVER print the token. - Playwright click actionability: an element behind an opaque overlay reports
visible, enabled and stablebut clicks retry until timeout ("intercepts pointer events") — when a click times out with a healthy-looking log, look for an overlay in the error context snapshot before touching the selector. - ws.ts setStatus transition-only bug (FIXED 2026-08-19): onStatus was only called on
connectedTRANSITIONS and the initial value isfalse— a socket that never opens (dead daemon from boot) fires close events without any transition, so the UI was never told (status dot stayed green after health overwrote it). Rule: notify on EVERY open/close event; the UI decides, backoff gaps make spamming impossible. The failure-path e2e (routeWebSocket close × 2, then connectToServer) is what caught it. - Playwright 1.62.1 routeWebSocket facts (verified): WebSocketRoute has NO
accept();close()called before the connection is fully routed is a silent no-op (page socket stays open);page.unroute()does NOT remove WebSocket routes (matches continued after unroute — appears to be a 1.62 regression/limitation; there is no unrouteWebSocket). WORKING PATTERN for failure-path tests: one handler, counter-based — close() the first N connections, thenws.connectToServer()for the rest — tests repeated-failure recovery deterministically without any unroute. - Test fixture key discipline: loop-driven contract tests must use uniform fixture keys (
.ok/.invalidfor every channel). A missing key silently publishesundefined, which the fail-closed send-site drops — the "invalid" tests pass trivially and the valid tests fail with0 !== 1and no obvious cause. Validate fixtures against their schemas FIRST (standalone script) before blaming the server. - Egress audit (scripts/egress-audit.mjs, wired into the aggregate
testchain): scans browser/dist for literal remotefetch(/WebSocket(/EventSource(call-sites andws:///wss://strings (FAIL = real network paths); non-localhost URL STRINGS are INFO only — monaco embeds doc/license links (github.com, json-schema.org, unicode.org, aka.ms...) as data, not fetches. Requires a build first (npm run build:frontendprecedes it in the chain; ci-run-all now has 39 commands).
Phase 4 audit checklist (applied to the existing wiring)
npm run contractsemits openapi.json; drift test green; every existing route is in the doc with strict schemas.- Fixtures exist for all routes incl. error paths; daemon route tests and browser api client tests import the SAME fixtures.
- WS channel live: model status + log tail reach the UI; reconnect with jitter works; views unsubscribe on teardown.
- Playwright suite green: open/edit/save/find/replace-workspace/split flows, offline-mode suite with egress audit ON.
- Egress audit in place; zero non-localhost fetches in the built bundle (grep dist/ for http:// and https:// — only localhost allowed).
npm run check(incl. contracts drift + e2e smoke) green.
Signals
- GitHub stars
- 43
- Forks
- 14
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
aide-arch-wiring- Source
- github.com/anonymousnomad/covert-coder
github.com/anonymousnomad/covert-coder
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScripthandsontable-playwright-e2e
Skill · handsontable
The pick for End-to-end testingmstar-e2e
Skill · btspoony
The pick for End-to-end testingnodejs-backend-patterns
Skill · wshobson
The pick for Noderun-node-tests
Skill · hiroro-work
The pick for Node