# Runtime impact investigation — packaging bug under ndomo server

**Slug:** `investigate-server-runtime-impact`
**Plan:** 785d36bf-7097-46d8-9c3d-9fabb5a81fdf (investigate-server-runtime-impact)
**Tasks:** ea2a82b6-dc40-439d-85a1-0cc45c96ff44 (orderIndex 1, dep graph) + 186ac6e9-35e2-4d54-a104-ddf153c85004 (orderIndex 2, impact matrix)
**Mode:** cartographer + analyst (read-only)
**Scope:** src/http/** + src/db/* (used by routes) + package.json whitelist
**Date:** 2026-06-28
**Status complementario:** smith tasks 0 + 0.1 (runtime smoke) están pendientes; este análisis es 100% estático y se complementa con runtime errors cuando smith termine.

---

## TL;DR

El server HTTP de ndomo **NO puede arrancar bajo v0.2.6 publicado**. Root cause: el módulo `src/events/bus.ts` falta del whitelist y es importado por **5 db files** transitivamente usados por **3 routes** (`/api/events`, `/api/plans/*`, `/api/tasks/*`, `/api/sessions/*`). El primer import resolution ocurre en module-load time (top-level `import { bus } from "../events/bus.ts"`), por lo que cualquier HTTP request que toque una ruta protegida dispara un error de module resolution antes de ejecutar lógica.

- **0 / 7 features funcionales** sin fix (todas rotas o parcialmente rotas)
- **3 features bloquean server startup** (auth, plans, tasks, sessions — todas requieren events/bus)
- **1 feature con falla diferida** (`/api/events` falla solo al primer subscribe)
- **2 features independientes** (health, CORS, security headers) funcionan parcialmente si el server arranca, pero auth + Elysia composition depende de los routes que sí fallan

---

## 1. Server composition order (from `src/http/server.ts`)

```ts
new Elysia({ name: "ndomo-http" })
  .use(securityHeaders)               // global, no deps
  .use(corsMiddleware(httpConfig))    // global, no deps
  .use(healthRoute(db))               // no cross-dir deps (just bun:sqlite)
  .use(apiProtected)                  // ← composition root, breaks here
  .use(spaApp);                       // SPA fallback, no imports
```

`apiProtected` composition:
```ts
const apiProtected = new Elysia({ name: "api-protected" })
  .use(httpBasicAuth(httpConfig))     // src/http/auth.ts → src/config/schema.ts (ok)
  .use(plansRoute(db))                // src/http/routes/plans.ts → src/db/{plans,types}.ts → src/events/bus.ts (BREAK)
  .use(tasksRoute(db))                // src/http/routes/tasks.ts → src/db/{tasks,types}.ts → src/events/bus.ts (BREAK)
  .use(sessionsRoute(db))             // src/http/routes/sessions.ts → src/db/sessions.ts → src/events/bus.ts (BREAK)
  .use(eventsRoute({ ...sdkClient? })); // src/http/routes/events.ts → src/events/bus.ts (BREAK)
```

**Critical insight**: `auth`, `cors`, `health`, `securityHeaders`, `spaApp` have NO transitive deps to missing files. Only the 4 routes under `apiProtected` pull in `events/bus.ts` (directly or transitively via db writers).

---

## 2. Per-route dependency graph

### 2.1 `/api/events` → `src/http/routes/events.ts`

**Direct imports:**
- `@opencode-ai/sdk/client` (external npm — present in `dependencies`)
- `elysia` (external npm — present in `dependencies`)
- `../../events/bus.ts` ← **MISSING in v0.2.6 tarball** (root cause file)
- `../sse.ts` ← `src/http/sse.ts` (already in v0.2.6 via `src/http` whitelist)

**Transitive imports (via `events/bus.ts`):** none — bus.ts has no relative imports.

**Status under v0.2.6:** **BROKEN at module-load** (top-level `import { bus } from "../../events/bus.ts"`).

### 2.2 `/api/plans*` → `src/http/routes/plans.ts`

**Direct imports:**
- `bun:sqlite` (Bun builtin)
- `elysia` (external npm)
- `../../db/plans.ts` ← in v0.2.6 (via `src/db` whitelist)
- `../../db/types.ts` ← in v0.2.6 (via `src/db` whitelist)

**Transitive imports (via `db/plans.ts`):**
- `../events/bus.ts` ← **MISSING in v0.2.6** (line 14 of db/plans.ts)

**Status under v0.2.6:** **BROKEN at module-load** (db/plans.ts top-level `import { bus } from "../events/bus.ts"` fires when routes/plans.ts is loaded by Elysia composition).

### 2.3 `/api/tasks*` → `src/http/routes/tasks.ts`

**Direct imports:**
- `bun:sqlite`
- `elysia`
- `../../db/tasks.ts` ← in v0.2.6
- `../../db/types.ts` ← in v0.2.6

**Transitive imports (via `db/tasks.ts`):**
- `../events/bus.ts` ← **MISSING in v0.2.6** (line 14 of db/tasks.ts)

**Status under v0.2.6:** **BROKEN at module-load**.

### 2.4 `/api/sessions*` → `src/http/routes/sessions.ts`

**Direct imports:**
- `bun:sqlite`
- `elysia`
- `../../db/sessions.ts` ← in v0.2.6

**Transitive imports (via `db/sessions.ts`):**
- `../events/bus.ts` ← **MISSING in v0.2.6** (line 15 of db/sessions.ts)

**Status under v0.2.6:** **BROKEN at module-load**.

### 2.5 `/health` → `src/http/routes/health.ts`

**Direct imports:**
- `bun:sqlite`
- `elysia`
- `../../../package.json` (dynamic import with `with: { type: "json" }`) ← present in v0.2.6 (package.json is root + in whitelist)

**Transitive imports:** none.

**Status under v0.2.6:** **FUNCTIONAL** — but ONLY if the server composition reaches `.use(healthRoute(db))` without throwing. Since `.use(apiProtected)` is composed BEFORE `.use(spaApp)` but AFTER `.use(healthRoute(db))` (per server.ts:147-149), and Elysia lazily evaluates routes, the actual startup sequence depends on Elysia's compile phase. **Predicted**: health route register is fine, but a request to /api/* triggers apiProtected composition → throws → request 500.

### 2.6 Auth middleware → `src/http/auth.ts`

**Direct imports:**
- `node:crypto` (Node builtin — present in Bun)
- `elysia`
- `../config/schema.ts` ← in v0.2.6 (via `src/config` whitelist)

**Transitive imports:** none (config/schema.ts has no internal imports per scout's audit).

**Status under v0.2.6:** **FUNCTIONAL** — but only registered if apiProtected composition succeeds (which it doesn't, due to plans/tasks/sessions/events routes pulling events/bus.ts).

### 2.7 CORS middleware → `src/http/middleware/cors.ts`

**Direct imports:**
- `elysia`

**Transitive imports:** none.

**Status under v0.2.6:** **FUNCTIONAL** — global middleware, registered before apiProtected. Does NOT trigger events/bus import.

### 2.8 Security headers → `src/http/middleware/security-headers.ts`

**Direct imports:**
- `elysia`
- `../../config/schema.ts` ← in v0.2.6

**Transitive imports:** none.

**Status under v0.2.6:** **FUNCTIONAL**.

---

## 3. Missing-files matrix per route

| Route | Direct missing | Transitive missing | Total missing |
|---|---|---|---|
| `/api/events` | `src/events/bus.ts` | — | **1** |
| `/api/plans`, `/api/plans/search`, `/api/plans/:id` | — | `src/events/bus.ts` | **1** |
| `/api/tasks`, `/api/tasks/search`, `/api/tasks/:id` | — | `src/events/bus.ts` | **1** |
| `/api/sessions`, `/api/sessions/active`, `/api/sessions/:id` | — | `src/events/bus.ts` | **1** |
| `/health` | — | — | 0 |
| Auth middleware | — | — | 0 |
| CORS middleware | — | — | 0 |
| Security headers | — | — | 0 |

**Cross-cutting missing**: `src/events/bus.ts` is the single root-cause missing file affecting 4 routes (9 endpoints total).

**Additional root-level missing** (from previous audit, not strictly required for server runtime, but breaks `bunx ndomo` bin):
- `src/index.ts` (main entry)
- `src/plugin.ts` (./plugin export)
- `src/lib.ts` (used by index.ts)
- `src/mem/scoped.ts`
- `src/orchestrator/{background,scheduler,memory-hook,reconciler}.ts`
- `src/worktrees/{manager,state}.ts`

These are required if anyone imports `ndomo` as an OpenCode plugin, but the bin command (`bunx ndomo serve`) only needs the CLI subtree, which IS in the whitelist. The CLI bin loads `src/cli/serve.ts`, which loads `src/http/server.ts`, which fails on events/bus.ts.

---

## 4. Feature impact matrix (under v0.2.6 published)

Priority legend: **P0** = blocks startup | **P1** = breaks after startup | **P2** = degraded | **P3** = informational

| # | Feature | Endpoint(s) | Status v0.2.6 | Why | Priority | Fix by |
|---|---|---|---|---|---|---|
| 1 | SSE events push | GET /api/events | **BROKEN** | `events/bus.ts` missing → module resolution fails | **P0** | Blanket `src` |
| 2 | Plans CRUD (read) | GET /api/plans, /search, /:id | **BROKEN** | `db/plans.ts` imports `events/bus.ts` at module-load | **P0** | Blanket `src` |
| 3 | Tasks CRUD (read) | GET /api/tasks, /search, /:id | **BROKEN** | `db/tasks.ts` imports `events/bus.ts` at module-load | **P0** | Blanket `src` |
| 4 | Sessions CRUD (read) | GET /api/sessions, /active, /:id | **BROKEN** | `db/sessions.ts` imports `events/bus.ts` at module-load | **P0** | Blanket `src` |
| 5 | HTTP Basic Auth | (gates /api/* except /health + /api/events) | **PARTIAL** | Auth fn itself is OK, but `apiProtected` composition throws before auth can run | **P1** | Blanket `src` (auto-resolved) |
| 6 | Health probe | GET /health | **LIKELY OK** | No transitiv events/bus dep; depends on Elysia composition succeeding | **P2** | Smoke verify |
| 7 | CORS | (all routes via onRequest) | **OK** | No deps on missing files | **P3** | — |
| 8 | Security headers | (all routes via onRequest) | **OK** | No deps on missing files | **P3** | — |
| 9 | SPA fallback | GET /* | **OK (with caveat)** | No import deps, but `src/http/web/**` is gitignored → SPA assets 404 unless `web:build` run post-install | **P3** | Separate concern (install.ts) |

### Critical path analysis

```
bunx ndomo serve
  → src/cli/serve.ts                          [in v0.2.6 ✓]
    → src/http/server.ts                       [in v0.2.6 ✓]
      → .use(securityHeaders)                  [in v0.2.6 ✓]
      → .use(corsMiddleware)                   [in v0.2.6 ✓]
      → .use(healthRoute)                      [in v0.2.6 ✓]
      → .use(apiProtected)
        → .use(httpBasicAuth)                  [in v0.2.6 ✓]
        → .use(plansRoute)                     [THROWS — events/bus missing]
        ...
```

**First failure**: `src/http/routes/plans.ts` loads `src/db/plans.ts`, which executes `import { bus } from "../events/bus.ts"` at top level. Bun throws `Cannot find module '../../events/bus.ts'` (matches the error reported in the plan overview). The throw propagates up the Elysia composition chain, killing server startup entirely.

**Workaround under v0.2.6**: NONE. The server cannot start. The only usable CLI commands under v0.2.6 are those that don't transitively reach `events/bus.ts`:
- `bunx ndomo help` ✓
- `bunx ndomo status --plans` ✓ (status.ts has no cross-dir deps)
- `bunx ndomo vacuum` — needs `src/db/client.ts` (in v0.2.6), but might fail if any client.ts internals transitively import events/bus
- `bunx ndomo smoke` — same
- `bunx ndomo install` — same
- `bunx ndomo analyses` — needs `src/db/analyses.ts` (in v0.2.6)

Let me verify which of those db files transitively need events/bus:

| db file | In v0.2.6? | Imports events/bus? | Status |
|---|---|---|---|
| `client.ts` | ✓ | no | OK |
| `migrations.ts` | ✓ | no | OK |
| `schema.ts` | ✓ | no | OK |
| `types.ts` | ✓ | no | OK |
| `fts-escape.ts` | ✓ | no | OK |
| `plans.ts` | ✓ | YES (line 14) | BROKEN |
| `tasks.ts` | ✓ | YES (line 14) | BROKEN |
| `sessions.ts` | ✓ | YES (line 15) | BROKEN |
| `plan-create.ts` | ✓ | YES (line 15) | BROKEN |
| `plan-update-status.ts` | ✓ | YES (line 17) | BROKEN |
| `plan-archive.ts` | ✓ | no | OK |
| `shutdown.ts` | ✓ | no | OK |
| `auto-checkpoint.ts` | ✓ | no | OK |
| `analyses.ts` | ✓ | no | OK |
| `incidents.ts` | ✓ | no | OK |
| `rollbacks.ts` | ✓ | no | OK |
| `resolve-project-dir.ts` | ✓ | no | OK |
| `index.ts` | ✓ | no | OK |

**6 of 17 db files break** under v0.2.6 due to `events/bus.ts`. The 5 writers (`plans`, `tasks`, `sessions`, `plan-create`, `plan-update-status`) plus the route imports that hit `plans`/`tasks`/`sessions`.

---

## 5. Prioritized gap list

### P0 — blocks server startup (fix immediately)

1. **`src/events/bus.ts` missing from whitelist** — affects 9 endpoints (4 routes × events_route) and 5 db files transitively. Single root cause.

### P1 — breaks after startup (auto-resolved by P0 fix)

2. **`src/http/routes/{plans,tasks,sessions,events}.ts` cannot load** — these will compile and load once `events/bus.ts` is present. No separate fix needed.

3. **`src/http/auth.ts`** registers fine if P0 fixed.

### P2 — observable only with fix + smoke

4. **`/health` endpoint** — verify works post-fix via `bunx ndomo@X.Y.Z serve --no-auth &` then `curl localhost:4097/health`.

5. **SPA fallback** — verify `src/http/web/` resolution works post-fix if user runs `web:build`. Out of scope of packaging fix.

### P3 — separate concerns (not packaging)

6. **`src/cli/install.ts` does not run `web:build`** — separate plan/decision.

7. **`src/index.ts`, `src/plugin.ts`, `src/lib.ts`, `src/mem/`, `src/orchestrator/`, `src/worktrees/`** — missing but not required for `bunx ndomo serve` bin. Required for `bunx ndomo` as opencode plugin. Auto-resolved by blanket `src` (same fix).

---

## 6. Validation matrix for warden (post-fix smoke)

| # | Test | Expected | Where |
|---|---|---|---|
| 1 | `bunx ndomo@X.Y.Z help` | exits 0, prints help | any cwd |
| 2 | `bunx ndomo@X.Y.Z status --plans` | prints plan table or empty | project root |
| 3 | `bunx ndomo@X.Y.Z serve --no-auth --port 4097` then `curl localhost:4097/health` | 200 JSON `{status:"ok",...}` | project root |
| 4 | Same server, `curl localhost:4097/api/plans` | 200 JSON array | project root |
| 5 | Same server, `curl localhost:4097/api/sessions` | 200 JSON array | project root |
| 6 | Same server, `curl -N localhost:4097/api/events` | SSE stream, `event: hello` first | project root |
| 7 | `bunx ndomo@X.Y.Z serve --port 4098` without OPENCODE_SERVER_PASSWORD | exits 1 with auth error | project root |
| 8 | `bunx ndomo@X.Y.Z serve --no-auth --port 4098` then curl with auth-required endpoint | 200 (no auth) or 401 (auth on) | project root |
| 9 | `npm pack --dry-run` | tarball includes `src/events/bus.ts` and `src/index.ts` etc. | repo root |

---

## 7. Complementary analysis (from previous plan)

This investigation builds on `audit-packaging-src-coverage` (analysis id `2b7b325b-50dc-4d35-af70-2069784d5385`). Key cross-references:
- Total missing runtime files: 11 (per packaging audit)
- Of those, `src/events/bus.ts` is the ONLY one required for HTTP server runtime
- The other 10 (`src/index.ts`, `src/plugin.ts`, `src/lib.ts`, mem/orchestrator/worktrees) are required for OpenCode plugin import but NOT for `bunx ndomo serve`

This means: **fixing only `src/events/` in the whitelist would unblock the server, but the OpenCode plugin import path would still break**. The blanket `src` recommendation from the packaging audit fixes BOTH simultaneously.

---

## 8. Caveats & unknown behavior (requires runtime validation)

1. **Elysia composition timing**: It's POSSIBLE that Elysia's `.use()` is lazy and routes don't actually execute module-load until first request. If so, the server might start but every /api/* request would throw. Needs smith runtime smoke to confirm.

2. **TypeScript compilation order**: bun resolves transitive imports at runtime, not at compile time. There is no `tsc` build step (typecheck only). So the missing files only manifest at `bun run` / `bunx` time.

3. **Bun module resolution**: bun may resolve `.ts` extensions differently than node. Confirmed: `events/bus.ts` extension is required in the import path (`import { bus } from "../events/bus.ts"`). If the published tarball lacks `.ts` extensions in imports for some reason, that would be a SEPARATE bug. (Confirmed not the case — all imports in audited files use `.ts` extensions.)

4. **`src/db/auto-checkpoint.ts`**: not currently importing events/bus, but it might emit events at runtime. Needs verification if any P0 features depend on it.

---

## 9. Recommendation summary for downstream agents

| Agent | Task | Action |
|---|---|---|
| craftsman (f071a28e orderIndex 1) | Apply blanket `src` + .npmignore | Already specified — this audit confirms necessity for ALL 9 endpoints |
| warden (f071a28e orderIndex 2) | Smoke test post-publish | Use validation matrix §6 above |
| smith (785d36bf orderIndex 0, 0.1) | Runtime smoke + capture errors | Run `bun run src/cli/serve.ts` and capture stack traces; compare against predicted §2 paths |
| foreman | Decide on `src/cli/install.ts` web:build gap | P3 — separate plan |