|
| 1 | +# Implementation notes — agent-native upgrade |
| 2 | + |
| 3 | +Decisions, dependencies and deviations from the agent-upgrade master prompt. |
| 4 | +Everything else is documented user-facing in `AGENTS.md`, `GET |
| 5 | +/api/capabilities` and `GET /api/openapi.json`. |
| 6 | + |
| 7 | +## The R2 decision: how the browser UI stays authorized under TREEMAP_TOKEN |
| 8 | + |
| 9 | +**Approach: server-side session cookie, set when the UI page is served.** |
| 10 | +When `TREEMAP_TOKEN` is set, `GET /` (or `/index.html`) responds with an |
| 11 | +`HttpOnly; SameSite=Strict` cookie carrying the token, and the `/api` auth |
| 12 | +middleware accepts either `Authorization: Bearer <token>` or that cookie. |
| 13 | + |
| 14 | +Why this and not the alternatives: |
| 15 | + |
| 16 | +- The frontend is frozen (R1): its literal `fetch('/api/…')` calls and |
| 17 | + `new EventSource('/api/…')` cannot be taught to send headers — and |
| 18 | + `EventSource` *cannot* send custom headers at all, so header-only auth |
| 19 | + would silently kill every SSE stream the UI depends on. Cookies ride along |
| 20 | + on both automatically. |
| 21 | +- A "same-origin requests are exempt" rule would trust `Origin`/`Referer` |
| 22 | + headers, which any non-browser client can forge — that's an auth bypass, |
| 23 | + not an exemption. |
| 24 | + |
| 25 | +Threat model (stated plainly, also in AGENTS.md): the token gates API access |
| 26 | +for non-browser clients, and `SameSite=Strict` + CORS-off blocks cross-site |
| 27 | +browser attacks; anyone who can load the UI page itself gets a session. If |
| 28 | +the port is exposed beyond localhost, front it with a reverse proxy that |
| 29 | +authenticates page loads and terminates TLS. |
| 30 | + |
| 31 | +## New dependencies (and why so few) |
| 32 | + |
| 33 | +- `@modelcontextprotocol/sdk` — the official MCP implementation; hand-rolling |
| 34 | + the protocol would be invented risk. Runtime dep (the MCP server runs from |
| 35 | + `dist/` with production deps only). |
| 36 | +- `zod` — peer dependency of the SDK and the input-validation layer for the |
| 37 | + MCP tools' schemas. |
| 38 | +- **CORS is implemented in-tree** (`src/middleware/cors.ts`): the policy is a |
| 39 | + handful of headers; the MIT `cors` package would add a dependency for no |
| 40 | + behavior we need. |
| 41 | +- **Zero frontend dependencies added**; `public/index.html` is untouched. |
| 42 | + |
| 43 | +## Design choices worth knowing |
| 44 | + |
| 45 | +- `prepareOffload` was extracted from `startOffload` (same file, byte-same |
| 46 | + checks) so dry-run and real-run share one source of truth; `startOffload` |
| 47 | + accepts the prepared plan back to avoid planning twice. |
| 48 | +- The agent policy (`agent-policy.json`) is **deliberately not writable via |
| 49 | + the API** — a policy an agent could rewrite for itself would be theatre. |
| 50 | + `GET /api/policy` shows the resolved policy and the file path; the human |
| 51 | + edits the file. |
| 52 | +- MCP `scan_path` accepts either `path` (start) or `scanId` (keep waiting) — |
| 53 | + the polling pattern for scans that outlive one tool-call timeout. The same |
| 54 | + pattern applies to `find_duplicates` and `offload` (via `jobId`). |
| 55 | +- MCP tool responses skip MCP `outputSchema`: the structured payloads are |
| 56 | + typed against `src/models/types.ts` at compile time, which keeps one source |
| 57 | + of truth instead of a second, silently-driftable schema layer. |
| 58 | +- Audit entries record refusals too (`outcome: "refused"`, with the policy |
| 59 | + code) — an agent probing the rails leaves a trace. |
| 60 | +- In the container the server binds `0.0.0.0` (a 127.0.0.1 bind is |
| 61 | + unreachable through a published port); `docker-compose.yml` restores the |
| 62 | + localhost-only posture by publishing on `127.0.0.1:4280` — widen the |
| 63 | + mapping and set `TREEMAP_TOKEN` deliberately for remote use. Containers |
| 64 | + have no OS Trash, so the container profile is scan-and-analyze; the compose |
| 65 | + file suggests read-only data mounts. |
| 66 | + |
| 67 | +## Deviations from the prompt |
| 68 | + |
| 69 | +- **Phase 6 gate (`docker build`) could not be executed on this machine: |
| 70 | + Docker is not installed.** Everything Docker would validate short of the |
| 71 | + image layer itself was proven directly: the exact runtime recipe |
| 72 | + (`package.json` + `npm ci --omit=dev` + `dist/` + `public/` + |
| 73 | + `node dist/index.js`) was staged in a clean directory and served |
| 74 | + `/api/system`, `/api/capabilities` and the UI correctly with production |
| 75 | + dependencies only. Run `docker build .` on a Docker-equipped machine (or |
| 76 | + CI) to close the loop. |
| 77 | +- No other deviations: every existing endpoint's response shape is unchanged |
| 78 | + (golden + contract suites), and with all new env vars unset the server's |
| 79 | + behavior is byte-identical to before (explicit tests assert the historical |
| 80 | + shapes and header-free responses). |
0 commit comments