# REPORT — LSP stack in docker, wired into crush (MOPAC dev env) Date: 2026-08-28 20:15 | Home: ~/projects/meta/MOPAC/lsp/ | Status: DONE (4.5/5 servers with diagnostics; bash wired but server emits none upstream) ## What runs Five language servers, all containerized, built from `MOPAC/lsp/docker-compose.yml` (`docker compose --profile build build`), on the dedicated bridge network `mopac-lsp` (isolated from other stacks; outbound HTTPS allowed because gopls fetches Go modules via proxy.golang.org at analysis time). stdio servers: one `docker run -i --rm` container per crush session via wrapper scripts (KNEL-AIMiddleware pattern, PID-suffixed names for concurrent sessions). No ports published (stdio over pipes). No host mounts beyond the workspace: gopls mounts the go.mod project root at its host path (URI parity) + named cache volumes `mopac-lsp-gomodcache` / `mopac-lsp-gocache`; marksman mounts cwd read-only; node servers mount nothing. | Server | Version | Base (digest-pinned) | Diagnostics verified | |---|---|---|---| | gopls | v0.23.0 | golang:1.26-alpine sha256:28d89ee9... | YES — semantic (see proof) | | yaml-language-server | 1.24.0 | node:22-alpine sha256:c610fcdf... | YES — "Flow sequence ... must end with ]" on bad yaml | | dockerfile-language-server | 0.15.0 | node:22-alpine sha256:c610fcdf... | YES — "Unknown instruction: FRM" on bad Dockerfile | | marksman | 2026-02-08 | alpine:3.22 sha256:14358309... | YES — lsp_symbols on .md (server emits few diagnostics by design) | | bash-language-server | 5.6.0 | node:22-alpine sha256:c610fcdf... | WIRED — container starts, initialize+publishDiagnostics flow; **v5.6 upstream does not publish tree-sitter parse errors as diagnostics** (same behavior in the KNEL stack). Hover/symbols/completion work. | marksman correction to the TASK text: it is an F# project, not a Go binary. No Go module exists to build; used the official musl release binary, pinned by release tag + build-time sha256 verification, DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 (no ICU package needed). ## Crush wiring (how) `lsp` block merged into `~/.config/crush/crush.json` (global config; backup at `~/.config/crush/crush.json.bak-mopaclsp`). Canonical snippet kept in sync at `MOPAC/lsp/crush-lsp.json`. Entries: gopls (filetypes go/mod/sum/work, root_markers go.mod|go.work, timeout 120 — cold start may download modules), bash-language-server, yaml-language-server, dockerfile-language-server (root_markers Dockerfile*), markdown (= marksman). **No per-project setup is needed**: crush auto-starts the matching server when a matching file is opened/viewed/edited in a matching root. Version bumps require only editing the Dockerfile ARG + compose arg + wrapper image tag, then rebuild. Wiring gotchas (empirical, crush v0.87.0): - marksman entry must be keyed `markdown` (KNEL-style); keyed `marksman` it never matches .md files and no container ever spawns. - An LSP config entry keyed by catalog name inherits powernap defaults; explicit filetypes/root_markers in the entry are respected. - Fixed a real blocker in harness: go.mod was a module-less stub ("go 1.24" only) — gopls returns "no module path" and zero semantic diagnostics. Added `module git.knownelement.com/reachableceo/MOPAC/harness` (absorbed into harness commit 591d345 by the parallel phase-2 session). ## Proof (headless crush turns, 2026-08-28) 1. gopls on real harness code (`cmd/harness/main.go` with a temp injected error at line 113, restored after): `113:25 [gopls compiler][UnusedVar] declared and not used: x` `113:37 [gopls compiler][UndeclaredName] undefined: yy` (Earlier, pre-fix: `undefined: undefindedVariable` Error 7:14 on the scratch file — full pipeline incl. container spawn confirmed by docker ps polling during the turn.) 2. yaml: `line 3:1 Flow sequence in block collection must be sufficiently indented and end with a ]` (yaml-language-server). 3. dockerfile: 2 errors on `FRM alpine:3.22` — `No source image provided with FROM` + `Unknown instruction: FRM` (dockerfile-language-server). 4. markdown: `lsp_symbols` returned `String Title (line 1)` for the test .md. 5. bash: container spawns on .sh access; raw probe shows the server parses and logs `syntax error` via window/logMessage but publishes an empty diagnostics array (upstream behavior, not a wiring failure). Scratch files for re-verification kept in `MOPAC/lsp/verify-scratch/`; raw probes: `lsp-handshake-test.sh`, `lsp-diagnostics-probe.sh`. ## Node-exception list (supply-chain ledger) Sanctioned node-based exceptions — containerized only, never installed on host; base node:22-alpine digest-pinned (sha256:c610fcdfb1d5b4740dd70c284 ed3cb16bb857e0f7166196e36a5501df7a3aa32); npm pins installed at build time: - bash-language-server@5.6.0 (npm integrity sha512-DCuV+/BZAAozsp5...) - yaml-language-server@1.24.0 (sha512-+HGcwu4M7IC...) - dockerfile-language-server-nodejs@0.15.0 (sha512-iS3GUUAn1bC3s...) ## Failures / deviations - marksman mislabeled "Go binary" in TASK — it's F#; handled via pinned prebuilt musl binary (sha256-verified) instead of building from source. - gopls v0.23.0 requires go >= 1.26; initial golang:1.25-alpine base failed (`requires go >= 1.26.0`), switched to golang:1.26-alpine (digest recorded). - marksman musl binary needs libgcc/libstdc++ on alpine (added) and .NET ICU check bypassed via invariant-globalization env var. - Compose parses unquoted `2026-02-08` args as timestamps ("2026-02-08 00:00:00 +0000 UTC") — quote date-like build args (fixed in compose). - bash-language-server publishes no parse diagnostics (upstream); noted, options.bashIde.sourceErrorDiagnostics=true set anyway for future-proofing. - MOPAC/lsp is not yet under version control (MOPAC root has no git repo; harness/.git is separate). Not committed — awaiting founder instruction on which repo should own dev-env tooling. ## Ports None. All servers are stdio (docker run -i, no published ports). Network `mopac-lsp` exists for isolation + gopls egress only.